feat: CE-Akte mit Anhang IV + Tech-File Sections fuer alle 4 Projekte

- 9 Sections nach EU MVO 2023/1230 Anhang IV (alle approved)
- Store fixes: html_content, tenant_id, nullable columns
- Frontend: _constants.ts mit Section-Types extrahiert
- 65 Verifikationseintraege automatisch generiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-08 01:49:14 +02:00
parent 51d91d20ed
commit c27022d11b
3 changed files with 145 additions and 125 deletions
+38 -23
View File
@@ -14,8 +14,18 @@ import (
// Tech File Section Operations
// ============================================================================
// CreateTechFileSection creates a new section in the technical file
// CreateTechFileSection creates a new section in the technical file.
// tenantID is extracted from the project's owning tenant.
func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID, sectionType, title, content string) (*TechFileSection, error) {
// Resolve tenant_id from the project
var tenantID uuid.UUID
err := s.pool.QueryRow(ctx,
`SELECT tenant_id FROM iace_projects WHERE id = $1`, projectID,
).Scan(&tenantID)
if err != nil {
return nil, fmt.Errorf("resolve tenant_id for project %s: %w", projectID, err)
}
tf := &TechFileSection{
ID: uuid.New(),
ProjectID: projectID,
@@ -28,19 +38,19 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
UpdatedAt: time.Now().UTC(),
}
_, err := s.pool.Exec(ctx, `
_, err = s.pool.Exec(ctx, `
INSERT INTO iace_tech_file_sections (
id, project_id, section_type, title, content,
version, status, approved_by, approved_at, metadata,
id, project_id, tenant_id, section_type, title, html_content,
status, approved_by, approved_at, metadata,
created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9, $10,
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12
)
`,
tf.ID, tf.ProjectID, tf.SectionType, tf.Title, tf.Content,
tf.Version, string(tf.Status), uuid.Nil, nil, nil,
tf.ID, tf.ProjectID, tenantID, tf.SectionType, tf.Title, tf.Content,
string(tf.Status), "", nil, nil,
tf.CreatedAt, tf.UpdatedAt,
)
if err != nil {
@@ -50,12 +60,11 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
return tf, nil
}
// UpdateTechFileSection updates the content of a tech file section and bumps version
// UpdateTechFileSection updates the content of a tech file section
func (s *Store) UpdateTechFileSection(ctx context.Context, id uuid.UUID, content string) error {
_, err := s.pool.Exec(ctx, `
UPDATE iace_tech_file_sections SET
content = $2,
version = version + 1,
html_content = $2,
status = $3,
updated_at = NOW()
WHERE id = $1
@@ -69,19 +78,15 @@ func (s *Store) UpdateTechFileSection(ctx context.Context, id uuid.UUID, content
// ApproveTechFileSection marks a tech file section as approved
func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approvedBy string) error {
now := time.Now().UTC()
approvedByUUID, err := uuid.Parse(approvedBy)
if err != nil {
return fmt.Errorf("invalid approved_by UUID: %w", err)
}
_, err = s.pool.Exec(ctx, `
_, err := s.pool.Exec(ctx, `
UPDATE iace_tech_file_sections SET
status = $2,
approved_by = $3,
approved_at = $4,
updated_at = $4
WHERE id = $1
`, id, string(TechFileSectionStatusApproved), approvedByUUID, now)
`, id, string(TechFileSectionStatusApproved), approvedBy, now)
if err != nil {
return fmt.Errorf("approve tech file section: %w", err)
}
@@ -93,11 +98,13 @@ func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approv
func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) ([]TechFileSection, error) {
rows, err := s.pool.Query(ctx, `
SELECT
id, project_id, section_type, title, content,
version, status, approved_by, approved_at, metadata,
created_at, updated_at
id, project_id, section_type, title,
COALESCE(html_content, '') AS content, status,
COALESCE(approved_by, '') AS approved_by, approved_at,
COALESCE(metadata, '{}'::jsonb) AS metadata,
created_at, COALESCE(updated_at, created_at) AS updated_at
FROM iace_tech_file_sections WHERE project_id = $1
ORDER BY section_type ASC, created_at ASC
ORDER BY created_at ASC
`, projectID)
if err != nil {
return nil, fmt.Errorf("list tech file sections: %w", err)
@@ -108,11 +115,13 @@ func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) (
for rows.Next() {
var tf TechFileSection
var status string
var approvedByStr string
var metadata []byte
err := rows.Scan(
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title, &tf.Content,
&tf.Version, &status, &tf.ApprovedBy, &tf.ApprovedAt, &metadata,
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title,
&tf.Content, &status,
&approvedByStr, &tf.ApprovedAt, &metadata,
&tf.CreatedAt, &tf.UpdatedAt,
)
if err != nil {
@@ -120,6 +129,12 @@ func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) (
}
tf.Status = TechFileSectionStatus(status)
tf.Version = 1
if approvedByStr != "" {
if parsed, e := uuid.Parse(approvedByStr); e == nil {
tf.ApprovedBy = parsed
}
}
json.Unmarshal(metadata, &tf.Metadata)
sections = append(sections, tf)