New project-bound submission drafts now default to a sortable, legal-
convention title instead of the bare "Entwurf N" counter:
<YYYY-MM-DD> <ClientName> ./. <ForumShort> ./. <OpponentName>
- Date leads (ISO, Europe/Berlin) so drafts list chronologically; " ./. "
is the German legal "gegen" separator.
- Client = root 'client' ancestor of the project tree.
- Forum = proceeding-type jurisdiction (UPC/EPA/DPMA); German proceedings
resolve to the deciding court (LG/OLG/BGH/BPatG) from the code tail.
- Opponent = primary opposing party, picked by our_side posture
(active → defendant bucket, reactive → claimant bucket).
- Any segment that resolves empty is omitted with its leading separator;
a project-less draft keeps the legacy "Entwurf N" scheme entirely.
- Create-time only: existing drafts are never renamed, and a lawyer's
later manual rename via Update is untouched. Same-slot collisions
de-duplicate with a " (N)" suffix.
Customization scope (per-user / firm / template, issue #155 Q4) is v1.1 —
the template is hardcoded in submission_autoname.go for now; the override
string is documented as the single extension point on AutoSubmissionTitle.
Example output:
full: 2026-05-31 Bayer AG ./. UPC ./. Novartis Pharma
no opponent: 2026-05-31 Bayer AG ./. BPatG
no forum: 2026-05-31 Bayer AG ./. Novartis Pharma
date only: 2026-05-31
AutoSubmissionTitle + segment resolvers are pure and table-tested
(submission_autoname_test.go); the Create flow is covered end-to-end
against real Postgres in submission_draft_autoname_live_test.go (gated
on TEST_DATABASE_URL).
130 lines
4.4 KiB
Go
130 lines
4.4 KiB
Go
package services
|
|
|
|
// Live-DB test for the submission-draft auto-naming scheme
|
|
// (t-paliad-352 / m/paliad#155). Skipped without TEST_DATABASE_URL.
|
|
//
|
|
// Verifies the shipped Create flow end-to-end against real Postgres:
|
|
// a project-bound draft is auto-named "<date> <client> ./. <forum> ./.
|
|
// <opponent>" rather than "Entwurf N", the segments resolve from the
|
|
// real project tree (client = root ancestor, forum = proceeding-type
|
|
// jurisdiction, opponent = opposing party by our_side), and a second
|
|
// draft on the same slot de-duplicates with a " (2)" suffix.
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/lib/pq"
|
|
|
|
"mgit.msbls.de/m/paliad/internal/db"
|
|
)
|
|
|
|
func TestSubmissionDraft_AutoName_Live(t *testing.T) {
|
|
url := os.Getenv("TEST_DATABASE_URL")
|
|
if url == "" {
|
|
t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
|
|
}
|
|
if err := db.ApplyMigrations(url); err != nil {
|
|
t.Fatalf("apply migrations: %v", err)
|
|
}
|
|
pool, err := sqlx.Connect("postgres", url)
|
|
if err != nil {
|
|
t.Fatalf("connect: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
ctx := context.Background()
|
|
|
|
userID := uuid.New()
|
|
email := "autoname-" + userID.String()[:8] + "@hlc.com"
|
|
var clientID, caseID uuid.UUID
|
|
cleanup := func() {
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.submission_drafts WHERE user_id = $1`, userID)
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.parties WHERE project_id = $1`, caseID)
|
|
// Children first (FK), then root.
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE user_id = $1`, userID)
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, caseID)
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, clientID)
|
|
pool.ExecContext(ctx, `DELETE FROM paliad.users WHERE id = $1`, userID)
|
|
pool.ExecContext(ctx, `DELETE FROM auth.users WHERE id = $1`, userID)
|
|
}
|
|
defer cleanup()
|
|
if _, err := pool.ExecContext(ctx, `INSERT INTO auth.users (id, email) VALUES ($1, $2)`, userID, email); err != nil {
|
|
t.Fatalf("seed auth.users: %v", err)
|
|
}
|
|
if _, err := pool.ExecContext(ctx,
|
|
`INSERT INTO paliad.users (id, email, display_name, office, global_role, lang)
|
|
VALUES ($1, $2, 'Auto Name', 'munich', 'standard', 'de')`, userID, email); err != nil {
|
|
t.Fatalf("seed paliad.users: %v", err)
|
|
}
|
|
|
|
users := NewUserService(pool)
|
|
projects := NewProjectService(pool, users)
|
|
parties := NewPartyService(pool, projects)
|
|
vars := NewSubmissionVarsService(pool, projects, parties, users)
|
|
renderer := NewSubmissionRenderer()
|
|
drafts := NewSubmissionDraftService(pool, projects, vars, renderer)
|
|
|
|
// Client root → case child. The case carries the proceeding type
|
|
// (UPC) and our_side (claimant), the party is the opponent.
|
|
client, err := projects.Create(ctx, userID, CreateProjectInput{
|
|
Type: "client", Title: "Bayer AG",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create client project: %v", err)
|
|
}
|
|
clientID = client.ID
|
|
|
|
ptID := 8 // upc.inf.cfi → jurisdiction UPC
|
|
side := "claimant"
|
|
caseProj, err := projects.Create(ctx, userID, CreateProjectInput{
|
|
Type: "case", Title: "Streitsache", ParentID: &client.ID,
|
|
ProceedingTypeID: &ptID, OurSide: &side,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create case project: %v", err)
|
|
}
|
|
caseID = caseProj.ID
|
|
|
|
beklagte := "Beklagte"
|
|
if _, err := parties.Create(ctx, userID, caseProj.ID, CreatePartyInput{
|
|
Name: "Novartis Pharma", Role: &beklagte,
|
|
}); err != nil {
|
|
t.Fatalf("create party: %v", err)
|
|
}
|
|
|
|
loc, _ := time.LoadLocation("Europe/Berlin")
|
|
today := time.Now().In(loc).Format("2006-01-02")
|
|
wantBase := today + " Bayer AG ./. UPC ./. Novartis Pharma"
|
|
|
|
d1, err := drafts.Create(ctx, userID, &caseProj.ID, "upc.inf.cfi.sod", "de")
|
|
if err != nil {
|
|
t.Fatalf("create draft 1: %v", err)
|
|
}
|
|
if d1.Name != wantBase {
|
|
t.Fatalf("draft 1 name = %q, want %q", d1.Name, wantBase)
|
|
}
|
|
|
|
// Second draft on the same (project, code) slot must de-duplicate.
|
|
d2, err := drafts.Create(ctx, userID, &caseProj.ID, "upc.inf.cfi.sod", "de")
|
|
if err != nil {
|
|
t.Fatalf("create draft 2: %v", err)
|
|
}
|
|
want2 := wantBase + " (2)"
|
|
if d2.Name != want2 {
|
|
t.Fatalf("draft 2 name = %q, want %q", d2.Name, want2)
|
|
}
|
|
|
|
// A project-less draft keeps the legacy Entwurf-N counter.
|
|
dless, err := drafts.Create(ctx, userID, nil, "upc.inf.cfi.sod", "de")
|
|
if err != nil {
|
|
t.Fatalf("create project-less draft: %v", err)
|
|
}
|
|
if dless.Name != "Entwurf 1" {
|
|
t.Fatalf("project-less draft name = %q, want %q", dless.Name, "Entwurf 1")
|
|
}
|
|
}
|