diff --git a/Portifolio b/Portifolio index bccb166..61841b5 100755 Binary files a/Portifolio and b/Portifolio differ diff --git a/app.db b/app.db index 4d8c3da..ce7c245 100644 Binary files a/app.db and b/app.db differ diff --git a/internal/database/main.go b/internal/database/main.go index b261c64..c32598d 100644 --- a/internal/database/main.go +++ b/internal/database/main.go @@ -55,7 +55,8 @@ func InitDB(db *sql.DB) { FOREIGN KEY (company_id) REFERENCES companies(id), FOREIGN KEY (currency_id) REFERENCES currencies(id), FOREIGN KEY (category_id) REFERENCES category(id), - FOREIGN KEY (period_id) REFERENCES periods(id) + FOREIGN KEY (period_id) REFERENCES periods(id), + UNIQUE(company_id, category_id, period_id) );` if _, err := db.Exec(schema); err != nil { @@ -63,3 +64,37 @@ func InitDB(db *sql.DB) { } fmt.Println("Tables ready") } + +func MigrateAddUniqueToRevenueEntries(db *sql.DB) error { + steps := []string{ + // 1. copy existing data into a temp table with the new constraint + `CREATE TABLE IF NOT EXISTS revenue_entries_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + company_id INTEGER NOT NULL, + currency_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + period_id INTEGER NOT NULL, + value REAL NOT NULL, + FOREIGN KEY (company_id) REFERENCES companies(id), + FOREIGN KEY (currency_id) REFERENCES currencies(id), + FOREIGN KEY (category_id) REFERENCES category(id), + FOREIGN KEY (period_id) REFERENCES periods(id), + UNIQUE(company_id, category_id, period_id) + )`, + // 2. copy data over + `INSERT OR IGNORE INTO revenue_entries_new + SELECT id, company_id, currency_id, category_id, period_id, value + FROM revenue_entries`, + // 3. drop old table + `DROP TABLE revenue_entries`, + // 4. rename new table + `ALTER TABLE revenue_entries_new RENAME TO revenue_entries`, + } + + for _, step := range steps { + if _, err := db.Exec(step); err != nil { + return fmt.Errorf("migration failed: %w", err) + } + } + return nil +} diff --git a/internal/database/revenue.go b/internal/database/revenue.go index f76da0d..f033ac0 100644 --- a/internal/database/revenue.go +++ b/internal/database/revenue.go @@ -57,3 +57,59 @@ func GetPeriodByID(db *sql.DB, periodID int) (model.Period, error) { p.End, _ = time.Parse("2006-01-02", end) return p, nil } + +func GetRevenueByPeriod(db *sql.DB, companyID int, periodID int) ([]model.Revenue, error) { + rows, err := db.Query( + `SELECT id, company_id, currency_id, period_id, value FROM revenue_entries WHERE company_id = ? AND period_id = ?`, + companyID, periodID, + ) + if err != nil { + return nil, fmt.Errorf("query revenue by period: %w", err) + } + defer rows.Close() + + var revenues []model.Revenue + for rows.Next() { + var rc model.Revenue + if err := rows.Scan(&rc.ID, &rc.Company, &rc.Currency, &rc.Period, &rc.Value); err != nil { + return nil, fmt.Errorf("scan revenue row: %w", err) + } + revenues = append(revenues, rc) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate revenue rows: %w", err) + } + if len(revenues) == 0 { + return nil, fmt.Errorf("revenue by company %d and period_id %d not found", companyID, periodID) + } + + return revenues, nil +} + +func GetRevenueByCategory(db *sql.DB, companyID int, categoryID int) ([]model.Revenue, error) { + rows, err := db.Query( + `SELECT id, company_id, currency_id, category_id, value FROM revenue_entries WHERE company_id = ? AND category_id = ?`, + companyID, categoryID, + ) + if err != nil { + return nil, fmt.Errorf("query revenue by category: %w", err) + } + defer rows.Close() + + var revenues []model.Revenue + for rows.Next() { + var rc model.Revenue + if err := rows.Scan(&rc.ID, &rc.Company, &rc.Currency, &rc.Category, &rc.Value); err != nil { + return nil, fmt.Errorf("scan revenue row: %w", err) + } + revenues = append(revenues, rc) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate revenue rows: %w", err) + } + if len(revenues) == 0 { + return nil, fmt.Errorf("revenue by company %d and category_id %d not found", companyID, categoryID) + } + + return revenues, nil +} diff --git a/internal/handlers/revenue.go b/internal/handlers/revenue.go index 9e75b44..6e62adb 100644 --- a/internal/handlers/revenue.go +++ b/internal/handlers/revenue.go @@ -6,7 +6,6 @@ import ( "database/sql" "encoding/json" "net/http" - "strconv" _ "github.com/mattn/go-sqlite3" ) @@ -51,6 +50,7 @@ func AddRevenueEntryHandler(db *sql.DB) http.HandlerFunc { } } +/* func GetRevenueReportHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { companyID, _ := strconv.Atoi(r.URL.Query().Get("company_id")) @@ -58,7 +58,7 @@ func GetRevenueReportHandler(db *sql.DB) http.HandlerFunc { year, _ := strconv.Atoi(r.URL.Query().Get("year")) idx, _ := strconv.Atoi(r.URL.Query().Get("index")) - entries, err := service.GetRevenue(db, companyID, model.PeriodType(periodType), year, idx) + entries, err := database.GetRevenueByPeriod(db, companyID, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -68,3 +68,4 @@ func GetRevenueReportHandler(db *sql.DB) http.HandlerFunc { json.NewEncoder(w).Encode(entries) } } +*/ diff --git a/internal/service/revenue.go b/internal/service/revenue.go index b995b91..371207d 100644 --- a/internal/service/revenue.go +++ b/internal/service/revenue.go @@ -5,7 +5,6 @@ import ( "Portifolio/internal/model" "database/sql" "fmt" - "time" _ "github.com/mattn/go-sqlite3" ) @@ -54,46 +53,3 @@ func InsertRevenue(db *sql.DB, companyID int, currencyID int, categoryName strin return nil } - -func GetRevenue(db *sql.DB, companyID int, periodType model.PeriodType, year, idx int) ([]model.Revenue, error) { - rows, err := db.Query(` - WITH RECURSIVE tree AS ( - SELECT id, name, parent_id - FROM category - WHERE company_id = ? AND parent_id IS NULL - - UNION ALL - - SELECT c.id, c.name, c.parent_id - FROM category c - JOIN tree t ON c.parent_id = t.id - ) - SELECT e.id, t.name, e.value, - p.type, p.year, p.idx, p.start_date, p.end_date - FROM revenue_entries e - JOIN tree t ON e.category_id = t.id - JOIN periods p ON e.period_id = p.id - WHERE e.company_id = ? AND p.type = ? AND p.year = ? AND p.idx = ?`, - companyID, companyID, string(periodType), year, idx, - ) - if err != nil { - return nil, fmt.Errorf("query revenue: %w", err) - } - defer rows.Close() - - var entries []model.Revenue - for rows.Next() { - var e model.Revenue - var p model.Period - var start, end string - if err := rows.Scan(&e.ID, &e.Category, &e.Value, - &p.Type, &p.Year, &p.Index, &start, &end); err != nil { - return nil, fmt.Errorf("scan revenue row: %w", err) - } - p.Start, _ = time.Parse("2006-01-02", start) - p.End, _ = time.Parse("2006-01-02", end) - e.Period = &p - entries = append(entries, e) - } - return entries, rows.Err() -} diff --git a/internal/shell/revenue.go b/internal/shell/revenue.go index 3a05ae9..95a838d 100644 --- a/internal/shell/revenue.go +++ b/internal/shell/revenue.go @@ -122,7 +122,7 @@ func ListRevenue(scanner *bufio.Scanner, db *sql.DB) { return } - entries, err := service.GetRevenue(db, companyID, period.Type, period.Year, period.Index) + entries, err := database.GetRevenueByPeriod(db, companyID, period.ID) if err != nil { fmt.Println(" ✗ Error:", err) return diff --git a/main.go b/main.go index 9cc4110..10624bd 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ func main() { } database.InitDB(db) + database.MigrateAddUniqueToRevenueEntries(db) fmt.Println("Connected to SQLite database") http.HandleFunc("/health", handlers.HealthHandler(db)) @@ -43,7 +44,7 @@ func main() { // Revenue http.HandleFunc("POST /add/revenue/entry", handlers.AddRevenueEntryHandler(db)) - http.HandleFunc("GET /revenue/report", handlers.GetRevenueReportHandler(db)) + //http.HandleFunc("GET /revenue/report", handlers.GetRevenueReportHandler(db)) fmt.Println("Server running on :8080") go func() {