ERP/cmd/seed/main.go

225 lines
6.3 KiB
Go
Raw Permalink Normal View History

2026-02-06 19:21:32 +01:00
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)
}