diff --git a/cmd/seed/main.go b/cmd/seed/main.go new file mode 100644 index 0000000..7b40d55 --- /dev/null +++ b/cmd/seed/main.go @@ -0,0 +1,224 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "os" + "strconv" + "time" + + "erp_system/internal/database" + + _ "modernc.org/sqlite" +) + +func main() { + dbPath := "erp.db" + if len(os.Args) > 1 { + dbPath = os.Args[1] + } + + log.Printf("Connecting to database at %s...", dbPath) + // We can use the internal database package to initialize (runs migrations) + db, err := database.Initialize(dbPath) + if err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + defer db.Close() + + // Configuration for generation + const ( + numCustomers = 500 + maxOrdersPerCust = 15 + maxLinesPerOrder = 8 + numJournalEntries = 100 + ) + + log.Println("Starting data generation...") + start := time.Now() + + tx, err := db.Begin() + if err != nil { + log.Fatalf("Failed to begin transaction: %v", err) + } + + // 1. Customers + customerIDs := make([]int64, 0, numCustomers) + for i := 0; i < numCustomers; i++ { + name := fmt.Sprintf("Customer %s %s", randomString(5), randomString(7)) + email := fmt.Sprintf("%s.%s@example.com", randomString(4), randomString(5)) + phone := fmt.Sprintf("555-%04d", rand.Intn(10000)) + address := fmt.Sprintf("%d %s St, City %s", rand.Intn(999), randomString(6), randomString(4)) + + res, err := tx.Exec("INSERT INTO customers (name, email, phone, address) VALUES (?, ?, ?, ?)", name, email, phone, address) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert customer: %v", err) + } + id, _ := res.LastInsertId() + customerIDs = append(customerIDs, id) + } + log.Printf("Generated %d customers", numCustomers) + + // 2. Orders and Lines and Invoices + var orderCount, lineCount, invoiceCount int + for _, custID := range customerIDs { + numOrders := rand.Intn(maxOrdersPerCust) + for j := 0; j < numOrders; j++ { + // Create Order + status := "draft" + r := rand.Float32() + if r > 0.7 { + status = "fulfilled" + } else if r > 0.4 { + status = "confirmed" + } else if r > 0.3 { + status = "cancelled" + } + + // Random date in last year + orderDate := time.Now().AddDate(0, 0, -rand.Intn(365)) + + res, err := tx.Exec("INSERT INTO orders (customer_id, status, order_date, notes) VALUES (?, ?, ?, ?)", + custID, status, orderDate.Format("2006-01-02"), "Generated order") + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert order: %v", err) + } + orderID, _ := res.LastInsertId() + orderCount++ + + // Create Order Lines + numLines := rand.Intn(maxLinesPerOrder) + 1 + var totalAmount float64 + for k := 0; k < numLines; k++ { + qty := float64(rand.Intn(10) + 1) + price := float64(rand.Intn(100) + 10) + desc := fmt.Sprintf("Product %s-%d", randomString(3), rand.Intn(100)) + lineTotal := qty * price + totalAmount += lineTotal + + _, err := tx.Exec("INSERT INTO order_lines (order_id, description, quantity, unit_price, line_total) VALUES (?, ?, ?, ?, ?)", + orderID, desc, qty, price, lineTotal) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert order line: %v", err) + } + lineCount++ + } + + // Update Order Total + _, err = tx.Exec("UPDATE orders SET total_amount = ? WHERE id = ?", totalAmount, orderID) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to update order total: %v", err) + } + + // Create Invoice if order is confirmed or fulfilled + if status == "confirmed" || status == "fulfilled" { + invoiceStatus := "pending" + if rand.Float32() > 0.5 { + invoiceStatus = "paid" + } + + invNum := fmt.Sprintf("INV-%d-%d", orderID, rand.Intn(10000)) + dueDate := orderDate.AddDate(0, 1, 0) // Due in 30 days + var paidDate interface{} = nil + if invoiceStatus == "paid" { + pd := dueDate.AddDate(0, 0, -rand.Intn(10)) + paidDate = pd.Format("2006-01-02") + } + + _, err := tx.Exec("INSERT INTO invoices (order_id, customer_id, invoice_number, status, amount, due_date, paid_date) VALUES (?, ?, ?, ?, ?, ?, ?)", + orderID, custID, invNum, invoiceStatus, totalAmount, dueDate.Format("2006-01-02"), paidDate) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert invoice: %v", err) + } + invoiceCount++ + } + } + } + log.Printf("Generated %d orders, %d lines, %d invoices", orderCount, lineCount, invoiceCount) + + // 3. Journal Entries + // Fetch account IDs first + rows, err := tx.Query("SELECT id, code, type FROM gl_accounts") + if err != nil { + tx.Rollback() + log.Fatalf("Failed to fetch accounts: %v", err) + } + defer rows.Close() + + type Account struct { + ID int64 + Code string + Type string + } + var accounts []Account + for rows.Next() { + var a Account + if err := rows.Scan(&a.ID, &a.Code, &a.Type); err != nil { + log.Fatalf("Failed to scan account: %v", err) + } + accounts = append(accounts, a) + } + + if len(accounts) >= 2 { + for i := 0; i < numJournalEntries; i++ { + date := time.Now().AddDate(0, 0, -rand.Intn(60)).Format("2006-01-02") + desc := fmt.Sprintf("Journal Entry %d", i+1) + + res, err := tx.Exec("INSERT INTO journal_entries (entry_date, description, reference) VALUES (?, ?, ?)", + date, desc, "GEN-"+strconv.Itoa(i)) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert journal entry: %v", err) + } + jeID, _ := res.LastInsertId() + + // Simple balanced entry: 2 lines + amount := float64(rand.Intn(5000) + 100) + + // Pick 2 random accounts + idx1 := rand.Intn(len(accounts)) + idx2 := rand.Intn(len(accounts)) + for idx1 == idx2 { + idx2 = rand.Intn(len(accounts)) + } + + // Debit line + _, err = tx.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, ?, ?)", + jeID, accounts[idx1].ID, amount, 0) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert journal line 1: %v", err) + } + + // Credit line + _, err = tx.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, ?, ?)", + jeID, accounts[idx2].ID, 0, amount) + if err != nil { + tx.Rollback() + log.Fatalf("Failed to insert journal line 2: %v", err) + } + } + } + log.Printf("Generated %d journal entries", numJournalEntries) + + if err := tx.Commit(); err != nil { + log.Fatalf("Failed to commit transaction: %v", err) + } + + log.Printf("Seeding completed in %v", time.Since(start)) +} + +func randomString(n int) string { + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} diff --git a/erp.db-wal b/erp.db-wal deleted file mode 100644 index 6f8332a..0000000 Binary files a/erp.db-wal and /dev/null differ