Files
breakpilot-compliance/ai-compliance-sdk/internal/roadmap/store_import.go
Sharang Parnerkar 3fb5b94905 refactor(go): split portfolio, workshop, training/models, roadmap stores
portfolio/store.go (818 LOC) → store_portfolio.go, store_items.go, store_metrics.go
workshop/store.go (793 LOC) → store_sessions.go, store_participants.go, store_responses.go
training/models.go (757 LOC) → models_enums.go, models_core.go, models_api.go, models_blocks.go
roadmap/store.go (757 LOC) → store_roadmap.go, store_items.go, store_import.go

All files under 350 LOC. Zero behavior changes, same package declarations.
go vet passes on all five packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:49:31 +02:00

214 lines
5.7 KiB
Go

package roadmap
import (
"context"
"encoding/json"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// ============================================================================
// Import Job Operations
// ============================================================================
// CreateImportJob creates a new import job
func (s *Store) CreateImportJob(ctx context.Context, job *ImportJob) error {
job.ID = uuid.New()
job.CreatedAt = time.Now().UTC()
job.UpdatedAt = job.CreatedAt
if job.Status == "" {
job.Status = "pending"
}
parsedItems, _ := json.Marshal(job.ParsedItems)
_, err := s.pool.Exec(ctx, `
INSERT INTO roadmap_import_jobs (
id, tenant_id, roadmap_id,
filename, format, file_size, content_type,
status, error_message,
total_rows, valid_rows, invalid_rows, imported_items,
parsed_items,
created_at, updated_at, completed_at, created_by
) VALUES (
$1, $2, $3,
$4, $5, $6, $7,
$8, $9,
$10, $11, $12, $13,
$14,
$15, $16, $17, $18
)
`,
job.ID, job.TenantID, job.RoadmapID,
job.Filename, string(job.Format), job.FileSize, job.ContentType,
job.Status, job.ErrorMessage,
job.TotalRows, job.ValidRows, job.InvalidRows, job.ImportedItems,
parsedItems,
job.CreatedAt, job.UpdatedAt, job.CompletedAt, job.CreatedBy,
)
return err
}
// GetImportJob retrieves an import job by ID
func (s *Store) GetImportJob(ctx context.Context, id uuid.UUID) (*ImportJob, error) {
var job ImportJob
var format string
var parsedItems []byte
err := s.pool.QueryRow(ctx, `
SELECT
id, tenant_id, roadmap_id,
filename, format, file_size, content_type,
status, error_message,
total_rows, valid_rows, invalid_rows, imported_items,
parsed_items,
created_at, updated_at, completed_at, created_by
FROM roadmap_import_jobs WHERE id = $1
`, id).Scan(
&job.ID, &job.TenantID, &job.RoadmapID,
&job.Filename, &format, &job.FileSize, &job.ContentType,
&job.Status, &job.ErrorMessage,
&job.TotalRows, &job.ValidRows, &job.InvalidRows, &job.ImportedItems,
&parsedItems,
&job.CreatedAt, &job.UpdatedAt, &job.CompletedAt, &job.CreatedBy,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
job.Format = ImportFormat(format)
json.Unmarshal(parsedItems, &job.ParsedItems)
return &job, nil
}
// UpdateImportJob updates an import job
func (s *Store) UpdateImportJob(ctx context.Context, job *ImportJob) error {
job.UpdatedAt = time.Now().UTC()
parsedItems, _ := json.Marshal(job.ParsedItems)
_, err := s.pool.Exec(ctx, `
UPDATE roadmap_import_jobs SET
roadmap_id = $2,
status = $3, error_message = $4,
total_rows = $5, valid_rows = $6, invalid_rows = $7, imported_items = $8,
parsed_items = $9,
updated_at = $10, completed_at = $11
WHERE id = $1
`,
job.ID, job.RoadmapID,
job.Status, job.ErrorMessage,
job.TotalRows, job.ValidRows, job.InvalidRows, job.ImportedItems,
parsedItems,
job.UpdatedAt, job.CompletedAt,
)
return err
}
// ============================================================================
// Statistics
// ============================================================================
// GetRoadmapStats returns statistics for a roadmap
func (s *Store) GetRoadmapStats(ctx context.Context, roadmapID uuid.UUID) (*RoadmapStats, error) {
stats := &RoadmapStats{
ByStatus: make(map[string]int),
ByPriority: make(map[string]int),
ByCategory: make(map[string]int),
ByDepartment: make(map[string]int),
}
// Total count
s.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM roadmap_items WHERE roadmap_id = $1",
roadmapID).Scan(&stats.TotalItems)
// By status
rows, err := s.pool.Query(ctx,
"SELECT status, COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 GROUP BY status",
roadmapID)
if err == nil {
defer rows.Close()
for rows.Next() {
var status string
var count int
rows.Scan(&status, &count)
stats.ByStatus[status] = count
}
}
// By priority
rows, err = s.pool.Query(ctx,
"SELECT priority, COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 GROUP BY priority",
roadmapID)
if err == nil {
defer rows.Close()
for rows.Next() {
var priority string
var count int
rows.Scan(&priority, &count)
stats.ByPriority[priority] = count
}
}
// By category
rows, err = s.pool.Query(ctx,
"SELECT category, COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 GROUP BY category",
roadmapID)
if err == nil {
defer rows.Close()
for rows.Next() {
var category string
var count int
rows.Scan(&category, &count)
stats.ByCategory[category] = count
}
}
// By department
rows, err = s.pool.Query(ctx,
"SELECT COALESCE(department, 'Unassigned'), COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 GROUP BY department",
roadmapID)
if err == nil {
defer rows.Close()
for rows.Next() {
var dept string
var count int
rows.Scan(&dept, &count)
stats.ByDepartment[dept] = count
}
}
// Overdue items
s.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 AND planned_end < NOW() AND status NOT IN ('COMPLETED', 'DEFERRED')",
roadmapID).Scan(&stats.OverdueItems)
// Upcoming items (next 7 days)
s.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 AND planned_end BETWEEN NOW() AND NOW() + INTERVAL '7 days' AND status NOT IN ('COMPLETED', 'DEFERRED')",
roadmapID).Scan(&stats.UpcomingItems)
// Total effort
s.pool.QueryRow(ctx,
"SELECT COALESCE(SUM(effort_days), 0) FROM roadmap_items WHERE roadmap_id = $1",
roadmapID).Scan(&stats.TotalEffortDays)
// Progress
completedCount := stats.ByStatus[string(ItemStatusCompleted)]
if stats.TotalItems > 0 {
stats.Progress = (completedCount * 100) / stats.TotalItems
}
return stats, nil
}