165 lines
4.7 KiB
Go
165 lines
4.7 KiB
Go
|
|
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,
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
_, err = db.Exec("INSERT INTO users (username, password_hash) VALUES (?, ?)", "admin", string(hash))
|
||
|
|
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
|
||
|
|
}
|