added dividend db table and insert by trade

This commit is contained in:
samantha42
2026-04-06 07:36:45 +02:00
parent 31108a16d0
commit 57ae3cfb06
7 changed files with 120 additions and 48 deletions

Binary file not shown.

BIN
app.db

Binary file not shown.

View File

@@ -17,14 +17,26 @@ func InitDB(db *sql.DB) {
); );
CREATE TABLE IF NOT EXISTS trades ( CREATE TABLE IF NOT EXISTS trades (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL, symbol TEXT NOT NULL,
currency_code TEXT NOT NULL, currency_code TEXT NOT NULL,
shares INTEGER NOT NULL, shares INTEGER NOT NULL,
product INTEGER NOT NULL CHECK(product IN (0, 1, 2, 3)), product INTEGER NOT NULL CHECK(product IN (0, 1, 2, 3, 4)), -- added 4 for BondTrade
type INTEGER NOT NULL CHECK(type IN (0, 1)), type INTEGER NOT NULL CHECK(type IN (0, 1)), -- Buy=0, Sell=1 only; Dividend has its own table
price REAL NOT NULL, price REAL NOT NULL,
traded_at DATETIME NOT NULL traded_at DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS dividends (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
currency_code TEXT NOT NULL,
product INTEGER NOT NULL CHECK(product IN (0, 1, 2, 3, 4)),
value REAL NOT NULL,
tax_amount REAL NOT NULL DEFAULT 0,
tax_rate REAL NOT NULL DEFAULT 0,
net_value REAL NOT NULL,
payment_date DATETIME NOT NULL
); );
CREATE TABLE IF NOT EXISTS position ( CREATE TABLE IF NOT EXISTS position (

View File

@@ -25,14 +25,7 @@ func GetTrades(db *sql.DB) ([]model.Trade, error) {
return nil, err return nil, err
} }
switch typeInt { t.Type = model.TradeType(typeInt)
case 0:
t.Type = model.TradeType(false)
case 1:
t.Type = model.TradeType(true)
default:
return nil, fmt.Errorf("failed to convert given Type int to bool of trade type.")
}
trades = append(trades, t) trades = append(trades, t)
} }
@@ -81,6 +74,21 @@ func InsertTrade(db *sql.DB, trade model.Trade) error {
return err return err
} }
func InsertDividend(db *sql.DB, div model.Dividend) error {
_, err := db.Exec(
"INSERT INTO trades (symbol, currency_code, shares, product, value, tax_amount, tax_rate, net_value, payment_date) VALUES (?, ?, ?, ?, ?, ?, ?)",
div.Symbol,
div.CurrencyCode,
div.Product,
div.Value,
div.TaxAmount,
div.TaxRate,
div.NetValue,
div.PaymentDate,
)
return err
}
func UpdatePositions(db *sql.DB, positions []model.Position) error { func UpdatePositions(db *sql.DB, positions []model.Position) error {
// Complete overwrite of the db positions // Complete overwrite of the db positions
_, err := db.Exec("DELETE FROM position") _, err := db.Exec("DELETE FROM position")

View File

@@ -20,45 +20,57 @@ func AddTradeHandler(db *sql.DB) http.HandlerFunc {
return return
} }
err := req.Validate() if err := req.Validate(); err != nil {
if err != nil { http.Error(w, fmt.Sprintf("failed to validate trade: %s", err), http.StatusBadRequest)
http.Error(w, fmt.Sprintf("failed to validate trade: %s", err), http.StatusInternalServerError)
return return
} }
// check if currency is in the db.
currency, err := database.GetCurrencyByCode(db, req.CurrencyCode) currency, err := database.GetCurrencyByCode(db, req.CurrencyCode)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to find currency: %s", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("failed to find currency: %s", err), http.StatusInternalServerError)
return return
} }
trade := model.Trade{ switch model.TradeType(req.Type) {
Symbol: req.Symbol, case model.DividendType:
Shares: req.Shares, dividend, err := req.ToDividend()
Product: model.TradeProduct(req.Product), if err != nil {
Type: model.TradeType(req.Type), http.Error(w, fmt.Sprintf("failed to build dividend: %s", err), http.StatusBadRequest)
Price: req.Price, return
CurrencyCode: currency.Code, }
Date: req.Date, dividend.CurrencyCode = currency.Code
}
err = database.InsertTrade(db, trade) if err := database.InsertDividend(db, dividend); err != nil {
if err != nil { http.Error(w, fmt.Sprintf("failed to insert dividend: %s", err), http.StatusInternalServerError)
http.Error(w, fmt.Sprintf("failed to insert trade into db: %s", err), http.StatusInternalServerError) return
return }
}
err = service.UpdatePositionByTradeList(db) w.Header().Set("Content-Type", "application/json")
update := true json.NewEncoder(w).Encode(map[string]any{"success": true})
if err != nil {
update = false
}
w.Header().Set("Content-Type", "application/json") case model.BuyType, model.SellType:
if err := json.NewEncoder(w).Encode(map[string]any{"success": true, "position update": update}); err != nil { trade, err := req.ToTrade()
http.Error(w, fmt.Sprintf("failed to encode trades: %s", err), http.StatusInternalServerError) if err != nil {
return http.Error(w, fmt.Sprintf("failed to build trade: %s", err), http.StatusBadRequest)
return
}
trade.CurrencyCode = currency.Code
if err := database.InsertTrade(db, trade); err != nil {
http.Error(w, fmt.Sprintf("failed to insert trade: %s", err), http.StatusInternalServerError)
return
}
update := true
if err := service.UpdatePositionByTradeList(db); err != nil {
update = false
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"success": true, "position_update": update})
default:
http.Error(w, fmt.Sprintf("unknown trade type: %d", req.Type), http.StatusBadRequest)
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package model
import ( import (
"errors" "errors"
"fmt"
"time" "time"
) )
@@ -55,21 +56,28 @@ const (
BondTrade BondTrade
) )
type TradeType bool type TradeType int
const ( const (
Buy TradeType = true BuyType TradeType = iota // 0
Sell TradeType = false SellType // 1
DividendType // 2
) )
type AddTradeRequest struct { type AddTradeRequest struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Shares int `json:"shares"` Shares int `json:"shares"`
Product int `json:"product"` Product int `json:"product"`
Type bool `json:"type"` Type int `json:"type"` // was bool, now int
Price float64 `json:"price"` Price float64 `json:"price"`
CurrencyCode string `json:"currency_code"` CurrencyCode string `json:"currency_code"`
Date time.Time `json:"date"` Date time.Time `json:"date"`
// Dividend-specific fields (only populated when Type == 2)
TaxAmount float64 `json:"tax_amount,omitempty"`
TaxRate float64 `json:"tax_rate,omitempty"`
NetValue float64 `json:"net_value,omitempty"`
PaymentDate time.Time `json:"payment_date,omitempty"`
} }
type Trade struct { type Trade struct {
@@ -107,6 +115,38 @@ func (r *AddTradeRequest) Validate() error {
return nil return nil
} }
func (r AddTradeRequest) ToDividend() (Dividend, error) {
if TradeType(r.Type) != DividendType {
return Dividend{}, fmt.Errorf("trade type is not a dividend")
}
return Dividend{
Symbol: r.Symbol,
CurrencyCode: r.CurrencyCode,
Product: TradeProduct(r.Product),
Value: r.Price, // gross value
PaymentDate: r.PaymentDate,
TaxAmount: r.TaxAmount,
TaxRate: r.TaxRate,
NetValue: r.NetValue,
}, nil
}
func (r AddTradeRequest) ToTrade() (Trade, error) {
t := TradeType(r.Type)
if t != BuyType && t != SellType {
return Trade{}, fmt.Errorf("trade type is not buy or sell")
}
return Trade{
Symbol: r.Symbol,
CurrencyCode: r.CurrencyCode,
Shares: r.Shares,
Product: TradeProduct(r.Product),
Type: t,
Price: r.Price,
Date: r.Date,
}, nil
}
// for now trades and none stock position will not be supported. // for now trades and none stock position will not be supported.
type Portifolio struct { type Portifolio struct {
Positions []Position Positions []Position

View File

@@ -19,7 +19,7 @@ func UpdatePositionByTradeList(db *sql.DB) error {
TradeSum := make(map[string]model.Position) TradeSum := make(map[string]model.Position)
for _, trade := range trades { for _, trade := range trades {
if trade.Type == model.Buy { if trade.Type == model.BuyType {
TradeSum[trade.Symbol] = model.Position{ TradeSum[trade.Symbol] = model.Position{
Symbol: trade.Symbol, Symbol: trade.Symbol,
CurrencyCode: trade.CurrencyCode, CurrencyCode: trade.CurrencyCode,