// Package ledger - Real client for aamos-ledger integration // Uses direct DB connection for reliability package ledger import ( "context" "database/sql" "fmt" "time" _ "github.com/lib/pq" ) // RealClient connects directly to aamos-ledger database type RealClient struct { db *sql.DB } // NewRealClient creates a client connected to ledger DB func NewRealClient(dbURL string) (*RealClient, error) { db, err := sql.Open("postgres", dbURL) if err != nil { return nil, fmt.Errorf("failed to connect to ledger DB: %w", err) } db.SetMaxOpenConns(10) db.SetMaxIdleConns(5) db.SetConnMaxLifetime(5 * time.Minute) if err := db.Ping(); err != nil { return nil, fmt.Errorf("failed to ping ledger DB: %w", err) } return &RealClient{db: db}, nil } // Account represents a BAS account type Account struct { Code string `json:"code"` Name string `json:"name"` AccountType string `json:"account_type"` Balance float64 `json:"balance"` } // GetAccounts returns all BAS accounts func (c *RealClient) GetAccounts(ctx context.Context) ([]Account, error) { rows, err := c.db.QueryContext(ctx, ` SELECT a.code, a.name, a.account_type, COALESCE(SUM(CASE WHEN jl.debit IS NOT NULL THEN jl.debit ELSE 0 END) - SUM(CASE WHEN jl.credit IS NOT NULL THEN jl.credit ELSE 0 END), 0) as balance FROM accounts a LEFT JOIN journal_lines jl ON a.id = jl.account_id GROUP BY a.id, a.code, a.name, a.account_type ORDER BY a.code `) if err != nil { return nil, fmt.Errorf("query accounts: %w", err) } defer rows.Close() var accounts []Account for rows.Next() { var a Account if err := rows.Scan(&a.Code, &a.Name, &a.AccountType, &a.Balance); err != nil { return nil, fmt.Errorf("scan account: %w", err) } accounts = append(accounts, a) } return accounts, rows.Err() } // GetBalanceSheet returns assets, liabilities, equity func (c *RealClient) GetBalanceSheet(ctx context.Context) (map[string]interface{}, error) { accounts, err := c.GetAccounts(ctx) if err != nil { return nil, err } var assets, liabilities, equity []Account var totalAssets, totalLiabilities, totalEquity float64 for _, a := range accounts { switch a.AccountType { case "Asset": assets = append(assets, a) totalAssets += a.Balance case "Liability": liabilities = append(liabilities, a) totalLiabilities += a.Balance case "Equity": equity = append(equity, a) totalEquity += a.Balance } } return map[string]interface{}{ "assets": accountsToMaps(assets), "liabilities": accountsToMaps(liabilities), "equity": accountsToMaps(equity), "total_assets": totalAssets, "total_liabilities": totalLiabilities, "total_equity": totalEquity, "period": time.Now().Format("2006-01"), }, nil } // GetTrialBalance returns trial balance func (c *RealClient) GetTrialBalance(ctx context.Context) ([]map[string]interface{}, error) { accounts, err := c.GetAccounts(ctx) if err != nil { return nil, err } var result []map[string]interface{} for _, a := range accounts { result = append(result, map[string]interface{}{ "account_number": a.Code, "account_name": a.Name, "balance": a.Balance, "account_type": a.AccountType, }) } return result, nil } // Close closes the database connection func (c *RealClient) Close() error { return c.db.Close() } func accountsToMaps(accounts []Account) []map[string]interface{} { var result []map[string]interface{} for _, a := range accounts { result = append(result, map[string]interface{}{ "account": a.Code + " - " + a.Name, "amount": a.Balance, }) } return result }