225 lines
6.3 KiB
Go
225 lines
6.3 KiB
Go
|
|
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)
|
||
|
|
}
|