194 lines
6.2 KiB
Go
194 lines
6.2 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/go-chi/chi/v5"
|
||
|
|
)
|
||
|
|
|
||
|
|
type BankHandler struct {
|
||
|
|
DB *sql.DB
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewBankHandler(db *sql.DB) *BankHandler {
|
||
|
|
return &BankHandler{DB: db}
|
||
|
|
}
|
||
|
|
|
||
|
|
type BankAccount struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
BankName string `json:"bank_name"`
|
||
|
|
AccountNumber string `json:"account_number"`
|
||
|
|
IBAN string `json:"iban"`
|
||
|
|
BIC string `json:"bic"`
|
||
|
|
Currency string `json:"currency"`
|
||
|
|
Balance float64 `json:"balance"`
|
||
|
|
IsDefault bool `json:"is_default"`
|
||
|
|
Status string `json:"status"`
|
||
|
|
LastSync *time.Time `json:"last_sync"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type BankTransaction struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
AccountID string `json:"account_id"`
|
||
|
|
TransactionDate time.Time `json:"transaction_date"`
|
||
|
|
Amount float64 `json:"amount"`
|
||
|
|
Currency string `json:"currency"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
Counterparty string `json:"counterparty"`
|
||
|
|
Reference string `json:"reference"`
|
||
|
|
ExternalID string `json:"external_id"`
|
||
|
|
Status string `json:"status"`
|
||
|
|
MatchedToType string `json:"matched_to_type"`
|
||
|
|
MatchedToID string `json:"matched_to_id"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *BankHandler) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
||
|
|
rows, err := h.DB.Query(`
|
||
|
|
SELECT id, name, bank_name, account_number, iban, bic, currency, balance, is_default, status, last_sync, created_at
|
||
|
|
FROM boc_bank_accounts WHERE status = 'active' ORDER BY name
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
accounts := []BankAccount{}
|
||
|
|
for rows.Next() {
|
||
|
|
var a BankAccount
|
||
|
|
if err := rows.Scan(&a.ID, &a.Name, &a.BankName, &a.AccountNumber, &a.IBAN, &a.BIC, &a.Currency, &a.Balance, &a.IsDefault, &a.Status, &a.LastSync, &a.CreatedAt); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
accounts = append(accounts, a)
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"accounts": accounts,
|
||
|
|
"total": len(accounts),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *BankHandler) CreateAccount(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req BankAccount
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var id string
|
||
|
|
err := h.DB.QueryRow(`
|
||
|
|
INSERT INTO boc_bank_accounts (name, bank_name, account_number, iban, bic, currency, is_default)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||
|
|
RETURNING id
|
||
|
|
`, req.Name, req.BankName, req.AccountNumber, req.IBAN, req.BIC, req.Currency, req.IsDefault).Scan(&id)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to create account")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||
|
|
"id": id,
|
||
|
|
"message": "Bank account created",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *BankHandler) ListTransactions(w http.ResponseWriter, r *http.Request) {
|
||
|
|
accountID := r.URL.Query().Get("account_id")
|
||
|
|
status := r.URL.Query().Get("status")
|
||
|
|
|
||
|
|
var query string
|
||
|
|
var args []interface{}
|
||
|
|
|
||
|
|
if accountID != "" {
|
||
|
|
if status != "" {
|
||
|
|
query = `SELECT id, account_id, transaction_date, amount, currency, description, counterparty, reference, external_id, status, matched_to_type, matched_to_id, created_at FROM boc_bank_transactions WHERE account_id = $1 AND status = $2 ORDER BY transaction_date DESC LIMIT 200`
|
||
|
|
args = append(args, accountID, status)
|
||
|
|
} else {
|
||
|
|
query = `SELECT id, account_id, transaction_date, amount, currency, description, counterparty, reference, external_id, status, matched_to_type, matched_to_id, created_at FROM boc_bank_transactions WHERE account_id = $1 ORDER BY transaction_date DESC LIMIT 200`
|
||
|
|
args = append(args, accountID)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
query = `SELECT id, account_id, transaction_date, amount, currency, description, counterparty, reference, external_id, status, matched_to_type, matched_to_id, created_at FROM boc_bank_transactions ORDER BY transaction_date DESC LIMIT 200`
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := h.DB.Query(query, args...)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
transactions := []BankTransaction{}
|
||
|
|
for rows.Next() {
|
||
|
|
var t BankTransaction
|
||
|
|
if err := rows.Scan(&t.ID, &t.AccountID, &t.TransactionDate, &t.Amount, &t.Currency, &t.Description, &t.Counterparty, &t.Reference, &t.ExternalID, &t.Status, &t.MatchedToType, &t.MatchedToID, &t.CreatedAt); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
transactions = append(transactions, t)
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"transactions": transactions,
|
||
|
|
"total": len(transactions),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *BankHandler) SyncTransactions(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req struct {
|
||
|
|
AccountID string `json:"account_id"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Implement actual bank API sync (PSD2/Open Banking)
|
||
|
|
// For now, simulate sync
|
||
|
|
_, err := h.DB.Exec(`
|
||
|
|
UPDATE boc_bank_accounts SET last_sync = NOW() WHERE id = $1
|
||
|
|
`, req.AccountID)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to sync")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"message": "Sync completed",
|
||
|
|
"synced": 0,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *BankHandler) MatchTransaction(w http.ResponseWriter, r *http.Request) {
|
||
|
|
transactionID := chi.URLParam(r, "id")
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
MatchType string `json:"match_type"`
|
||
|
|
MatchID string `json:"match_id"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err := h.DB.Exec(`
|
||
|
|
UPDATE boc_bank_transactions
|
||
|
|
SET status = 'matched', matched_to_type = $1, matched_to_id = $2
|
||
|
|
WHERE id = $3
|
||
|
|
`, req.MatchType, req.MatchID, transactionID)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to match transaction")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"message": "Transaction matched",
|
||
|
|
})
|
||
|
|
}
|