ERP/internal/database/migrations.go

179 lines
5.3 KiB
Go
Raw Normal View History

2026-02-06 17:35:29 +01:00
package database
import (
"database/sql"
"fmt"
"log"
"golang.org/x/crypto/bcrypt"
)
func runMigrations(db *sql.DB) error {
migrations := []string{
// Users table
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
2026-02-08 14:20:18 +01:00
role TEXT NOT NULL DEFAULT 'user',
2026-02-06 17:35:29 +01:00
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// Customers table
`CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL DEFAULT '',
phone TEXT NOT NULL DEFAULT '',
address TEXT NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// Orders table
`CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
order_date DATE NOT NULL DEFAULT (date('now')),
total_amount REAL NOT NULL DEFAULT 0,
notes TEXT NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id)
)`,
// Order lines table
`CREATE TABLE IF NOT EXISTS order_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
description TEXT NOT NULL,
quantity REAL NOT NULL DEFAULT 1,
unit_price REAL NOT NULL DEFAULT 0,
line_total REAL NOT NULL DEFAULT 0,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
)`,
// Invoices table
`CREATE TABLE IF NOT EXISTS invoices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER,
customer_id INTEGER NOT NULL,
invoice_number TEXT NOT NULL UNIQUE,
status TEXT NOT NULL DEFAULT 'pending',
amount REAL NOT NULL DEFAULT 0,
due_date DATE NOT NULL,
paid_date DATE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (customer_id) REFERENCES customers(id)
)`,
// GL Accounts table (Chart of Accounts)
`CREATE TABLE IF NOT EXISTS gl_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
type TEXT NOT NULL,
balance REAL NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// Journal Entries table
`CREATE TABLE IF NOT EXISTS journal_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
entry_date DATE NOT NULL DEFAULT (date('now')),
description TEXT NOT NULL,
reference TEXT NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// Journal Lines table
`CREATE TABLE IF NOT EXISTS journal_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
journal_entry_id INTEGER NOT NULL,
account_id INTEGER NOT NULL,
debit REAL NOT NULL DEFAULT 0,
credit REAL NOT NULL DEFAULT 0,
FOREIGN KEY (journal_entry_id) REFERENCES journal_entries(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES gl_accounts(id)
)`,
}
for i, m := range migrations {
if _, err := db.Exec(m); err != nil {
return fmt.Errorf("migration %d failed: %w", i, err)
}
}
2026-02-08 14:20:18 +01:00
// Manual migration for existing users table
var roleExists int
db.QueryRow("SELECT COUNT(*) FROM pragma_table_info('users') WHERE name='role'").Scan(&roleExists)
if roleExists == 0 {
if _, err := db.Exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user'"); err != nil {
log.Printf("Failed to add role column: %v", err)
} else {
log.Println("Added role column to users table")
// Update existing admin user if exists
db.Exec("UPDATE users SET role = 'admin' WHERE username = 'admin'")
}
}
2026-02-06 17:35:29 +01:00
log.Println("Migrations completed")
return nil
}
func seedData(db *sql.DB) error {
// Seed admin user if not exists
var count int
db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
if count == 0 {
hash, err := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("hashing password: %w", err)
}
2026-02-08 14:20:18 +01:00
_, err = db.Exec("INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)", "admin", string(hash), "admin")
2026-02-06 17:35:29 +01:00
if err != nil {
return fmt.Errorf("inserting admin user: %w", err)
}
log.Println("Seeded admin user (admin / admin123)")
}
// Seed Chart of Accounts if not exists
db.QueryRow("SELECT COUNT(*) FROM gl_accounts").Scan(&count)
if count == 0 {
accounts := []struct {
Code, Name, Type string
}{
// Assets
{"1000", "Cash", "asset"},
{"1100", "Accounts Receivable", "asset"},
{"1200", "Inventory", "asset"},
// Liabilities
{"2000", "Accounts Payable", "liability"},
{"2100", "Sales Tax Payable", "liability"},
// Equity
{"3000", "Owner's Equity", "equity"},
{"3100", "Retained Earnings", "equity"},
// Revenue
{"4000", "Sales Revenue", "revenue"},
{"4100", "Service Revenue", "revenue"},
// Expenses
{"5000", "Cost of Goods Sold", "expense"},
{"5100", "Salaries Expense", "expense"},
{"5200", "Rent Expense", "expense"},
{"5300", "Utilities Expense", "expense"},
}
for _, a := range accounts {
_, err := db.Exec("INSERT INTO gl_accounts (code, name, type) VALUES (?, ?, ?)", a.Code, a.Name, a.Type)
if err != nil {
return fmt.Errorf("inserting account %s: %w", a.Code, err)
}
}
log.Println("Seeded chart of accounts")
}
return nil
}