mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-11-03 18:13:27 -05:00 
			
		
		
		
	Move splits/transactions to store
This commit is contained in:
		@@ -6,7 +6,6 @@ import (
 | 
				
			|||||||
	"github.com/aclindsa/moneygo/internal/models"
 | 
						"github.com/aclindsa/moneygo/internal/models"
 | 
				
			||||||
	"github.com/aclindsa/moneygo/internal/store/db"
 | 
						"github.com/aclindsa/moneygo/internal/store/db"
 | 
				
			||||||
	"github.com/yuin/gopher-lua"
 | 
						"github.com/yuin/gopher-lua"
 | 
				
			||||||
	"math/big"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,24 +167,29 @@ func luaAccountBalance(L *lua.LState) int {
 | 
				
			|||||||
		panic("SecurityId not in lua security_map")
 | 
							panic("SecurityId not in lua security_map")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	date := luaWeakCheckTime(L, 2)
 | 
						date := luaWeakCheckTime(L, 2)
 | 
				
			||||||
	var b Balance
 | 
						var splits *[]*models.Split
 | 
				
			||||||
	var rat *big.Rat
 | 
					 | 
				
			||||||
	if date != nil {
 | 
						if date != nil {
 | 
				
			||||||
		end := luaWeakCheckTime(L, 3)
 | 
							end := luaWeakCheckTime(L, 3)
 | 
				
			||||||
		if end != nil {
 | 
							if end != nil {
 | 
				
			||||||
			rat, err = GetAccountBalanceDateRange(tx, user, a.AccountId, date, end)
 | 
								splits, err = tx.GetAccountSplitsDateRange(user, a.AccountId, date, end)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			rat, err = GetAccountBalanceDate(tx, user, a.AccountId, date)
 | 
								splits, err = tx.GetAccountSplitsDate(user, a.AccountId, date)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		rat, err = GetAccountBalance(tx, user, a.AccountId)
 | 
							splits, err = tx.GetAccountSplits(user, a.AccountId)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic("Failed to GetAccountBalance:" + err.Error())
 | 
							panic("Failed to fetch splits for account:" + err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	b.Amount = rat
 | 
						rat, err := BalanceFromSplits(splits)
 | 
				
			||||||
	b.Security = security
 | 
						if err != nil {
 | 
				
			||||||
	L.Push(BalanceToLua(L, &b))
 | 
							panic("Failed to calculate balance for account:" + err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						b := &Balance{
 | 
				
			||||||
 | 
							Amount:   rat,
 | 
				
			||||||
 | 
							Security: security,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						L.Push(BalanceToLua(L, b))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 1
 | 
						return 1
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -437,7 +437,7 @@ func GnucashImportHandler(r *http.Request, context *Context) ResponseWriterWrite
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			split.AccountId = acctId
 | 
								split.AccountId = acctId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			exists, err := SplitAlreadyImported(context.Tx, split)
 | 
								exists, err := context.Tx.SplitExists(split)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print("Error checking if split was already imported:", err)
 | 
									log.Print("Error checking if split was already imported:", err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
@@ -446,7 +446,7 @@ func GnucashImportHandler(r *http.Request, context *Context) ResponseWriterWrite
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if !already_imported {
 | 
							if !already_imported {
 | 
				
			||||||
			err := InsertTransaction(context.Tx, &transaction, user)
 | 
								err := context.Tx.InsertTransaction(&transaction, user)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print(err)
 | 
									log.Print(err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -187,7 +187,7 @@ func ofxImportHelper(tx *db.Tx, r io.Reader, user *models.User, accountid int64)
 | 
				
			|||||||
				split.SecurityId = -1
 | 
									split.SecurityId = -1
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			exists, err := SplitAlreadyImported(tx, split)
 | 
								exists, err := tx.SplitExists(split)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print("Error checking if split was already imported:", err)
 | 
									log.Print("Error checking if split was already imported:", err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
@@ -202,7 +202,7 @@ func ofxImportHelper(tx *db.Tx, r io.Reader, user *models.User, accountid int64)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, transaction := range transactions {
 | 
						for _, transaction := range transactions {
 | 
				
			||||||
		err := InsertTransaction(tx, &transaction, user)
 | 
							err := tx.InsertTransaction(&transaction, user)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Print(err)
 | 
								log.Print(err)
 | 
				
			||||||
			return NewError(999 /*Internal Error*/)
 | 
								return NewError(999 /*Internal Error*/)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,22 +2,16 @@ package handlers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"github.com/aclindsa/moneygo/internal/models"
 | 
						"github.com/aclindsa/moneygo/internal/models"
 | 
				
			||||||
 | 
						"github.com/aclindsa/moneygo/internal/store"
 | 
				
			||||||
	"github.com/aclindsa/moneygo/internal/store/db"
 | 
						"github.com/aclindsa/moneygo/internal/store/db"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math/big"
 | 
						"math/big"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func SplitAlreadyImported(tx *db.Tx, s *models.Split) (bool, error) {
 | 
					 | 
				
			||||||
	count, err := tx.SelectInt("SELECT COUNT(*) from splits where RemoteId=? and AccountId=?", s.RemoteId, s.AccountId)
 | 
					 | 
				
			||||||
	return count == 1, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Return a map of security ID's to big.Rat's containing the amount that
 | 
					// Return a map of security ID's to big.Rat's containing the amount that
 | 
				
			||||||
// security is imbalanced by
 | 
					// security is imbalanced by
 | 
				
			||||||
func GetTransactionImbalances(tx *db.Tx, t *models.Transaction) (map[int64]big.Rat, error) {
 | 
					func GetTransactionImbalances(tx *db.Tx, t *models.Transaction) (map[int64]big.Rat, error) {
 | 
				
			||||||
@@ -64,219 +58,6 @@ func TransactionBalanced(tx *db.Tx, t *models.Transaction) (bool, error) {
 | 
				
			|||||||
	return true, nil
 | 
						return true, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetTransaction(tx *db.Tx, transactionid int64, userid int64) (*models.Transaction, error) {
 | 
					 | 
				
			||||||
	var t models.Transaction
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err := tx.SelectOne(&t, "SELECT * from transactions where UserId=? AND TransactionId=?", userid, transactionid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err = tx.Select(&t.Splits, "SELECT * from splits where TransactionId=?", transactionid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &t, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetTransactions(tx *db.Tx, userid int64) (*[]models.Transaction, error) {
 | 
					 | 
				
			||||||
	var transactions []models.Transaction
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err := tx.Select(&transactions, "SELECT * from transactions where UserId=?", userid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := range transactions {
 | 
					 | 
				
			||||||
		_, err := tx.Select(&transactions[i].Splits, "SELECT * from splits where TransactionId=?", transactions[i].TransactionId)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &transactions, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func incrementAccountVersions(tx *db.Tx, user *models.User, accountids []int64) error {
 | 
					 | 
				
			||||||
	for i := range accountids {
 | 
					 | 
				
			||||||
		account, err := tx.GetAccount(accountids[i], user.UserId)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		account.AccountVersion++
 | 
					 | 
				
			||||||
		count, err := tx.Update(account)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if count != 1 {
 | 
					 | 
				
			||||||
			return errors.New("Updated more than one account")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type AccountMissingError struct{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ame AccountMissingError) Error() string {
 | 
					 | 
				
			||||||
	return "Account missing"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func InsertTransaction(tx *db.Tx, t *models.Transaction, user *models.User) error {
 | 
					 | 
				
			||||||
	// Map of any accounts with transaction splits being added
 | 
					 | 
				
			||||||
	a_map := make(map[int64]bool)
 | 
					 | 
				
			||||||
	for i := range t.Splits {
 | 
					 | 
				
			||||||
		if t.Splits[i].AccountId != -1 {
 | 
					 | 
				
			||||||
			existing, err := tx.SelectInt("SELECT count(*) from accounts where AccountId=?", t.Splits[i].AccountId)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if existing != 1 {
 | 
					 | 
				
			||||||
				return AccountMissingError{}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			a_map[t.Splits[i].AccountId] = true
 | 
					 | 
				
			||||||
		} else if t.Splits[i].SecurityId == -1 {
 | 
					 | 
				
			||||||
			return AccountMissingError{}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	//increment versions for all accounts
 | 
					 | 
				
			||||||
	var a_ids []int64
 | 
					 | 
				
			||||||
	for id := range a_map {
 | 
					 | 
				
			||||||
		a_ids = append(a_ids, id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// ensure at least one of the splits is associated with an actual account
 | 
					 | 
				
			||||||
	if len(a_ids) < 1 {
 | 
					 | 
				
			||||||
		return AccountMissingError{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err := incrementAccountVersions(tx, user, a_ids)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.UserId = user.UserId
 | 
					 | 
				
			||||||
	err = tx.Insert(t)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := range t.Splits {
 | 
					 | 
				
			||||||
		t.Splits[i].TransactionId = t.TransactionId
 | 
					 | 
				
			||||||
		t.Splits[i].SplitId = -1
 | 
					 | 
				
			||||||
		err = tx.Insert(t.Splits[i])
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func UpdateTransaction(tx *db.Tx, t *models.Transaction, user *models.User) error {
 | 
					 | 
				
			||||||
	var existing_splits []*models.Split
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err := tx.Select(&existing_splits, "SELECT * from splits where TransactionId=?", t.TransactionId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Map of any accounts with transaction splits being added
 | 
					 | 
				
			||||||
	a_map := make(map[int64]bool)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Make a map with any existing splits for this transaction
 | 
					 | 
				
			||||||
	s_map := make(map[int64]bool)
 | 
					 | 
				
			||||||
	for i := range existing_splits {
 | 
					 | 
				
			||||||
		s_map[existing_splits[i].SplitId] = true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Insert splits, updating any pre-existing ones
 | 
					 | 
				
			||||||
	for i := range t.Splits {
 | 
					 | 
				
			||||||
		t.Splits[i].TransactionId = t.TransactionId
 | 
					 | 
				
			||||||
		_, ok := s_map[t.Splits[i].SplitId]
 | 
					 | 
				
			||||||
		if ok {
 | 
					 | 
				
			||||||
			count, err := tx.Update(t.Splits[i])
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if count > 1 {
 | 
					 | 
				
			||||||
				return fmt.Errorf("Updated %d transaction splits while attempting to update only 1", count)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			delete(s_map, t.Splits[i].SplitId)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			t.Splits[i].SplitId = -1
 | 
					 | 
				
			||||||
			err := tx.Insert(t.Splits[i])
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if t.Splits[i].AccountId != -1 {
 | 
					 | 
				
			||||||
			a_map[t.Splits[i].AccountId] = true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Delete any remaining pre-existing splits
 | 
					 | 
				
			||||||
	for i := range existing_splits {
 | 
					 | 
				
			||||||
		_, ok := s_map[existing_splits[i].SplitId]
 | 
					 | 
				
			||||||
		if existing_splits[i].AccountId != -1 {
 | 
					 | 
				
			||||||
			a_map[existing_splits[i].AccountId] = true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if ok {
 | 
					 | 
				
			||||||
			_, err := tx.Delete(existing_splits[i])
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Increment versions for all accounts with modified splits
 | 
					 | 
				
			||||||
	var a_ids []int64
 | 
					 | 
				
			||||||
	for id := range a_map {
 | 
					 | 
				
			||||||
		a_ids = append(a_ids, id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = incrementAccountVersions(tx, user, a_ids)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	count, err := tx.Update(t)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if count > 1 {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Updated %d transactions (expected 1)", count)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func DeleteTransaction(tx *db.Tx, t *models.Transaction, user *models.User) error {
 | 
					 | 
				
			||||||
	var accountids []int64
 | 
					 | 
				
			||||||
	_, err := tx.Select(&accountids, "SELECT DISTINCT AccountId FROM splits WHERE TransactionId=? AND AccountId != -1", t.TransactionId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err = tx.Exec("DELETE FROM splits WHERE TransactionId=?", t.TransactionId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	count, err := tx.Delete(t)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if count != 1 {
 | 
					 | 
				
			||||||
		return errors.New("Deleted more than one transaction")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = incrementAccountVersions(tx, user, accountids)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter {
 | 
					func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter {
 | 
				
			||||||
	user, err := GetUserFromSession(context.Tx, r)
 | 
						user, err := GetUserFromSession(context.Tx, r)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -311,9 +92,9 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
			return NewError(3 /*Invalid Request*/)
 | 
								return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		err = InsertTransaction(context.Tx, &transaction, user)
 | 
							err = context.Tx.InsertTransaction(&transaction, user)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			if _, ok := err.(AccountMissingError); ok {
 | 
								if _, ok := err.(store.AccountMissingError); ok {
 | 
				
			||||||
				return NewError(3 /*Invalid Request*/)
 | 
									return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				log.Print(err)
 | 
									log.Print(err)
 | 
				
			||||||
@@ -326,7 +107,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
		if context.LastLevel() {
 | 
							if context.LastLevel() {
 | 
				
			||||||
			//Return all Transactions
 | 
								//Return all Transactions
 | 
				
			||||||
			var al models.TransactionList
 | 
								var al models.TransactionList
 | 
				
			||||||
			transactions, err := GetTransactions(context.Tx, user.UserId)
 | 
								transactions, err := context.Tx.GetTransactions(user.UserId)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print(err)
 | 
									log.Print(err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
@@ -339,7 +120,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return NewError(3 /*Invalid Request*/)
 | 
									return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			transaction, err := GetTransaction(context.Tx, transactionid, user.UserId)
 | 
								transaction, err := context.Tx.GetTransaction(transactionid, user.UserId)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return NewError(3 /*Invalid Request*/)
 | 
									return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -377,7 +158,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			err = UpdateTransaction(context.Tx, &transaction, user)
 | 
								err = context.Tx.UpdateTransaction(&transaction, user)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print(err)
 | 
									log.Print(err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
@@ -385,12 +166,12 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			return &transaction
 | 
								return &transaction
 | 
				
			||||||
		} else if r.Method == "DELETE" {
 | 
							} else if r.Method == "DELETE" {
 | 
				
			||||||
			transaction, err := GetTransaction(context.Tx, transactionid, user.UserId)
 | 
								transaction, err := context.Tx.GetTransaction(transactionid, user.UserId)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return NewError(3 /*Invalid Request*/)
 | 
									return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			err = DeleteTransaction(context.Tx, transaction, user)
 | 
								err = context.Tx.DeleteTransaction(transaction, user)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Print(err)
 | 
									log.Print(err)
 | 
				
			||||||
				return NewError(999 /*Internal Error*/)
 | 
									return NewError(999 /*Internal Error*/)
 | 
				
			||||||
@@ -402,41 +183,9 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter
 | 
				
			|||||||
	return NewError(3 /*Invalid Request*/)
 | 
						return NewError(3 /*Invalid Request*/)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TransactionsBalanceDifference(tx *db.Tx, accountid int64, transactions []models.Transaction) (*big.Rat, error) {
 | 
					func BalanceFromSplits(splits *[]*models.Split) (*big.Rat, error) {
 | 
				
			||||||
	var pageDifference, tmp big.Rat
 | 
					 | 
				
			||||||
	for i := range transactions {
 | 
					 | 
				
			||||||
		_, err := tx.Select(&transactions[i].Splits, "SELECT * FROM splits where TransactionId=?", transactions[i].TransactionId)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Sum up the amounts from the splits we're returning so we can return
 | 
					 | 
				
			||||||
		// an ending balance
 | 
					 | 
				
			||||||
		for j := range transactions[i].Splits {
 | 
					 | 
				
			||||||
			if transactions[i].Splits[j].AccountId == accountid {
 | 
					 | 
				
			||||||
				rat_amount, err := models.GetBigAmount(transactions[i].Splits[j].Amount)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				tmp.Add(&pageDifference, rat_amount)
 | 
					 | 
				
			||||||
				pageDifference.Set(&tmp)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &pageDifference, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetAccountBalance(tx *db.Tx, user *models.User, accountid int64) (*big.Rat, error) {
 | 
					 | 
				
			||||||
	var splits []models.Split
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=?"
 | 
					 | 
				
			||||||
	_, err := tx.Select(&splits, sql, accountid, user.UserId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var balance, tmp big.Rat
 | 
						var balance, tmp big.Rat
 | 
				
			||||||
	for _, s := range splits {
 | 
						for _, s := range *splits {
 | 
				
			||||||
		rat_amount, err := models.GetBigAmount(s.Amount)
 | 
							rat_amount, err := models.GetBigAmount(s.Amount)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
@@ -448,132 +197,6 @@ func GetAccountBalance(tx *db.Tx, user *models.User, accountid int64) (*big.Rat,
 | 
				
			|||||||
	return &balance, nil
 | 
						return &balance, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Assumes accountid is valid and is owned by the current user
 | 
					 | 
				
			||||||
func GetAccountBalanceDate(tx *db.Tx, user *models.User, accountid int64, date *time.Time) (*big.Rat, error) {
 | 
					 | 
				
			||||||
	var splits []models.Split
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date < ?"
 | 
					 | 
				
			||||||
	_, err := tx.Select(&splits, sql, accountid, user.UserId, date)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var balance, tmp big.Rat
 | 
					 | 
				
			||||||
	for _, s := range splits {
 | 
					 | 
				
			||||||
		rat_amount, err := models.GetBigAmount(s.Amount)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tmp.Add(&balance, rat_amount)
 | 
					 | 
				
			||||||
		balance.Set(&tmp)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &balance, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetAccountBalanceDateRange(tx *db.Tx, user *models.User, accountid int64, begin, end *time.Time) (*big.Rat, error) {
 | 
					 | 
				
			||||||
	var splits []models.Split
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date >= ? AND transactions.Date < ?"
 | 
					 | 
				
			||||||
	_, err := tx.Select(&splits, sql, accountid, user.UserId, begin, end)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var balance, tmp big.Rat
 | 
					 | 
				
			||||||
	for _, s := range splits {
 | 
					 | 
				
			||||||
		rat_amount, err := models.GetBigAmount(s.Amount)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tmp.Add(&balance, rat_amount)
 | 
					 | 
				
			||||||
		balance.Set(&tmp)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &balance, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetAccountTransactions(tx *db.Tx, user *models.User, accountid int64, sort string, page uint64, limit uint64) (*models.AccountTransactionsList, error) {
 | 
					 | 
				
			||||||
	var transactions []models.Transaction
 | 
					 | 
				
			||||||
	var atl models.AccountTransactionsList
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var sqlsort, balanceLimitOffset string
 | 
					 | 
				
			||||||
	var balanceLimitOffsetArg uint64
 | 
					 | 
				
			||||||
	if sort == "date-asc" {
 | 
					 | 
				
			||||||
		sqlsort = " ORDER BY transactions.Date ASC, transactions.TransactionId ASC"
 | 
					 | 
				
			||||||
		balanceLimitOffset = " LIMIT ?"
 | 
					 | 
				
			||||||
		balanceLimitOffsetArg = page * limit
 | 
					 | 
				
			||||||
	} else if sort == "date-desc" {
 | 
					 | 
				
			||||||
		numSplits, err := tx.SelectInt("SELECT count(*) FROM splits")
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		sqlsort = " ORDER BY transactions.Date DESC, transactions.TransactionId DESC"
 | 
					 | 
				
			||||||
		balanceLimitOffset = fmt.Sprintf(" LIMIT %d OFFSET ?", numSplits)
 | 
					 | 
				
			||||||
		balanceLimitOffsetArg = (page + 1) * limit
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var sqloffset string
 | 
					 | 
				
			||||||
	if page > 0 {
 | 
					 | 
				
			||||||
		sqloffset = fmt.Sprintf(" OFFSET %d", page*limit)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	account, err := tx.GetAccount(accountid, user.UserId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	atl.Account = account
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sql := "SELECT DISTINCT transactions.* FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + " LIMIT ?" + sqloffset
 | 
					 | 
				
			||||||
	_, err = tx.Select(&transactions, sql, user.UserId, accountid, limit)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	atl.Transactions = &transactions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pageDifference, err := TransactionsBalanceDifference(tx, accountid, transactions)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	count, err := tx.SelectInt("SELECT count(DISTINCT transactions.TransactionId) FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?", user.UserId, accountid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	atl.TotalTransactions = count
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	security, err := tx.GetSecurity(atl.Account.SecurityId, user.UserId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if security == nil {
 | 
					 | 
				
			||||||
		return nil, errors.New("Security not found")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Sum all the splits for all transaction splits for this account that
 | 
					 | 
				
			||||||
	// occurred before the page we're returning
 | 
					 | 
				
			||||||
	var amounts []string
 | 
					 | 
				
			||||||
	sql = "SELECT s.Amount FROM splits AS s INNER JOIN (SELECT DISTINCT transactions.Date, transactions.TransactionId FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + balanceLimitOffset + ") as t ON s.TransactionId = t.TransactionId WHERE s.AccountId=?"
 | 
					 | 
				
			||||||
	_, err = tx.Select(&amounts, sql, user.UserId, accountid, balanceLimitOffsetArg, accountid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var tmp, balance big.Rat
 | 
					 | 
				
			||||||
	for _, amount := range amounts {
 | 
					 | 
				
			||||||
		rat_amount, err := models.GetBigAmount(amount)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tmp.Add(&balance, rat_amount)
 | 
					 | 
				
			||||||
		balance.Set(&tmp)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	atl.BeginningBalance = balance.FloatString(security.Precision)
 | 
					 | 
				
			||||||
	atl.EndingBalance = tmp.Add(&balance, pageDifference).FloatString(security.Precision)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &atl, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Return only those transactions which have at least one split pertaining to
 | 
					// Return only those transactions which have at least one split pertaining to
 | 
				
			||||||
// an account
 | 
					// an account
 | 
				
			||||||
func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter {
 | 
					func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter {
 | 
				
			||||||
@@ -609,7 +232,7 @@ func AccountTransactionsHandler(context *Context, r *http.Request, user *models.
 | 
				
			|||||||
		sort = sortstring
 | 
							sort = sortstring
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	accountTransactions, err := GetAccountTransactions(context.Tx, user, accountid, sort, page, limit)
 | 
						accountTransactions, err := context.Tx.GetAccountTransactions(user, accountid, sort, page, limit)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Print(err)
 | 
							log.Print(err)
 | 
				
			||||||
		return NewError(999 /*Internal Error*/)
 | 
							return NewError(999 /*Internal Error*/)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -276,7 +276,7 @@ func TestGetTransactions(t *testing.T) {
 | 
				
			|||||||
			found := false
 | 
								found := false
 | 
				
			||||||
			for _, tran := range *tl.Transactions {
 | 
								for _, tran := range *tl.Transactions {
 | 
				
			||||||
				if tran.TransactionId == curr.TransactionId {
 | 
									if tran.TransactionId == curr.TransactionId {
 | 
				
			||||||
					ensureTransactionsMatch(t, &curr, &tran, nil, true, true)
 | 
										ensureTransactionsMatch(t, &curr, tran, nil, true, true)
 | 
				
			||||||
					if _, ok := foundIds[tran.TransactionId]; ok {
 | 
										if _, ok := foundIds[tran.TransactionId]; ok {
 | 
				
			||||||
						continue
 | 
											continue
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -410,7 +410,7 @@ func helperTestAccountTransactions(t *testing.T, d *TestData, account *models.Ac
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		if atl.Transactions != nil {
 | 
							if atl.Transactions != nil {
 | 
				
			||||||
			for _, tran := range *atl.Transactions {
 | 
								for _, tran := range *atl.Transactions {
 | 
				
			||||||
				transactions = append(transactions, tran)
 | 
									transactions = append(transactions, *tran)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			lastFetchCount = int64(len(*atl.Transactions))
 | 
								lastFetchCount = int64(len(*atl.Transactions))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,12 +82,12 @@ type Transaction struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TransactionList struct {
 | 
					type TransactionList struct {
 | 
				
			||||||
	Transactions *[]Transaction `json:"transactions"`
 | 
						Transactions *[]*Transaction `json:"transactions"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AccountTransactionsList struct {
 | 
					type AccountTransactionsList struct {
 | 
				
			||||||
	Account           *Account
 | 
						Account           *Account
 | 
				
			||||||
	Transactions      *[]Transaction
 | 
						Transactions      *[]*Transaction
 | 
				
			||||||
	TotalTransactions int64
 | 
						TotalTransactions int64
 | 
				
			||||||
	BeginningBalance  string
 | 
						BeginningBalance  string
 | 
				
			||||||
	EndingBalance     string
 | 
						EndingBalance     string
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										361
									
								
								internal/store/db/transactions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								internal/store/db/transactions.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,361 @@
 | 
				
			|||||||
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/aclindsa/moneygo/internal/models"
 | 
				
			||||||
 | 
						"github.com/aclindsa/moneygo/internal/store"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) incrementAccountVersions(user *models.User, accountids []int64) error {
 | 
				
			||||||
 | 
						for i := range accountids {
 | 
				
			||||||
 | 
							account, err := tx.GetAccount(accountids[i], user.UserId)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							account.AccountVersion++
 | 
				
			||||||
 | 
							count, err := tx.Update(account)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if count != 1 {
 | 
				
			||||||
 | 
								return errors.New("Updated more than one account")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) InsertTransaction(t *models.Transaction, user *models.User) error {
 | 
				
			||||||
 | 
						// Map of any accounts with transaction splits being added
 | 
				
			||||||
 | 
						a_map := make(map[int64]bool)
 | 
				
			||||||
 | 
						for i := range t.Splits {
 | 
				
			||||||
 | 
							if t.Splits[i].AccountId != -1 {
 | 
				
			||||||
 | 
								existing, err := tx.SelectInt("SELECT count(*) from accounts where AccountId=?", t.Splits[i].AccountId)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if existing != 1 {
 | 
				
			||||||
 | 
									return store.AccountMissingError{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								a_map[t.Splits[i].AccountId] = true
 | 
				
			||||||
 | 
							} else if t.Splits[i].SecurityId == -1 {
 | 
				
			||||||
 | 
								return store.AccountMissingError{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//increment versions for all accounts
 | 
				
			||||||
 | 
						var a_ids []int64
 | 
				
			||||||
 | 
						for id := range a_map {
 | 
				
			||||||
 | 
							a_ids = append(a_ids, id)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// ensure at least one of the splits is associated with an actual account
 | 
				
			||||||
 | 
						if len(a_ids) < 1 {
 | 
				
			||||||
 | 
							return store.AccountMissingError{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := tx.incrementAccountVersions(user, a_ids)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.UserId = user.UserId
 | 
				
			||||||
 | 
						err = tx.Insert(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range t.Splits {
 | 
				
			||||||
 | 
							t.Splits[i].TransactionId = t.TransactionId
 | 
				
			||||||
 | 
							t.Splits[i].SplitId = -1
 | 
				
			||||||
 | 
							err = tx.Insert(t.Splits[i])
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) SplitExists(s *models.Split) (bool, error) {
 | 
				
			||||||
 | 
						count, err := tx.SelectInt("SELECT COUNT(*) from splits where RemoteId=? and AccountId=?", s.RemoteId, s.AccountId)
 | 
				
			||||||
 | 
						return count == 1, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) GetTransaction(transactionid int64, userid int64) (*models.Transaction, error) {
 | 
				
			||||||
 | 
						var t models.Transaction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := tx.SelectOne(&t, "SELECT * from transactions where UserId=? AND TransactionId=?", userid, transactionid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = tx.Select(&t.Splits, "SELECT * from splits where TransactionId=?", transactionid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &t, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) GetTransactions(userid int64) (*[]*models.Transaction, error) {
 | 
				
			||||||
 | 
						var transactions []*models.Transaction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := tx.Select(&transactions, "SELECT * from transactions where UserId=?", userid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range transactions {
 | 
				
			||||||
 | 
							_, err := tx.Select(&transactions[i].Splits, "SELECT * from splits where TransactionId=?", transactions[i].TransactionId)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &transactions, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) UpdateTransaction(t *models.Transaction, user *models.User) error {
 | 
				
			||||||
 | 
						var existing_splits []*models.Split
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := tx.Select(&existing_splits, "SELECT * from splits where TransactionId=?", t.TransactionId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Map of any accounts with transaction splits being added
 | 
				
			||||||
 | 
						a_map := make(map[int64]bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Make a map with any existing splits for this transaction
 | 
				
			||||||
 | 
						s_map := make(map[int64]bool)
 | 
				
			||||||
 | 
						for i := range existing_splits {
 | 
				
			||||||
 | 
							s_map[existing_splits[i].SplitId] = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Insert splits, updating any pre-existing ones
 | 
				
			||||||
 | 
						for i := range t.Splits {
 | 
				
			||||||
 | 
							t.Splits[i].TransactionId = t.TransactionId
 | 
				
			||||||
 | 
							_, ok := s_map[t.Splits[i].SplitId]
 | 
				
			||||||
 | 
							if ok {
 | 
				
			||||||
 | 
								count, err := tx.Update(t.Splits[i])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if count > 1 {
 | 
				
			||||||
 | 
									return fmt.Errorf("Updated %d transaction splits while attempting to update only 1", count)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								delete(s_map, t.Splits[i].SplitId)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								t.Splits[i].SplitId = -1
 | 
				
			||||||
 | 
								err := tx.Insert(t.Splits[i])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if t.Splits[i].AccountId != -1 {
 | 
				
			||||||
 | 
								a_map[t.Splits[i].AccountId] = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete any remaining pre-existing splits
 | 
				
			||||||
 | 
						for i := range existing_splits {
 | 
				
			||||||
 | 
							_, ok := s_map[existing_splits[i].SplitId]
 | 
				
			||||||
 | 
							if existing_splits[i].AccountId != -1 {
 | 
				
			||||||
 | 
								a_map[existing_splits[i].AccountId] = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if ok {
 | 
				
			||||||
 | 
								_, err := tx.Delete(existing_splits[i])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment versions for all accounts with modified splits
 | 
				
			||||||
 | 
						var a_ids []int64
 | 
				
			||||||
 | 
						for id := range a_map {
 | 
				
			||||||
 | 
							a_ids = append(a_ids, id)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = tx.incrementAccountVersions(user, a_ids)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count, err := tx.Update(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if count > 1 {
 | 
				
			||||||
 | 
							return fmt.Errorf("Updated %d transactions (expected 1)", count)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) DeleteTransaction(t *models.Transaction, user *models.User) error {
 | 
				
			||||||
 | 
						var accountids []int64
 | 
				
			||||||
 | 
						_, err := tx.Select(&accountids, "SELECT DISTINCT AccountId FROM splits WHERE TransactionId=? AND AccountId != -1", t.TransactionId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = tx.Exec("DELETE FROM splits WHERE TransactionId=?", t.TransactionId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count, err := tx.Delete(t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if count != 1 {
 | 
				
			||||||
 | 
							return errors.New("Deleted more than one transaction")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = tx.incrementAccountVersions(user, accountids)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) GetAccountSplits(user *models.User, accountid int64) (*[]*models.Split, error) {
 | 
				
			||||||
 | 
						var splits []*models.Split
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=?"
 | 
				
			||||||
 | 
						_, err := tx.Select(&splits, sql, accountid, user.UserId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &splits, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Assumes accountid is valid and is owned by the current user
 | 
				
			||||||
 | 
					func (tx *Tx) GetAccountSplitsDate(user *models.User, accountid int64, date *time.Time) (*[]*models.Split, error) {
 | 
				
			||||||
 | 
						var splits []*models.Split
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date < ?"
 | 
				
			||||||
 | 
						_, err := tx.Select(&splits, sql, accountid, user.UserId, date)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &splits, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) GetAccountSplitsDateRange(user *models.User, accountid int64, begin, end *time.Time) (*[]*models.Split, error) {
 | 
				
			||||||
 | 
						var splits []*models.Split
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date >= ? AND transactions.Date < ?"
 | 
				
			||||||
 | 
						_, err := tx.Select(&splits, sql, accountid, user.UserId, begin, end)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &splits, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) transactionsBalanceDifference(accountid int64, transactions []*models.Transaction) (*big.Rat, error) {
 | 
				
			||||||
 | 
						var pageDifference, tmp big.Rat
 | 
				
			||||||
 | 
						for i := range transactions {
 | 
				
			||||||
 | 
							_, err := tx.Select(&transactions[i].Splits, "SELECT * FROM splits where TransactionId=?", transactions[i].TransactionId)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Sum up the amounts from the splits we're returning so we can return
 | 
				
			||||||
 | 
							// an ending balance
 | 
				
			||||||
 | 
							for j := range transactions[i].Splits {
 | 
				
			||||||
 | 
								if transactions[i].Splits[j].AccountId == accountid {
 | 
				
			||||||
 | 
									rat_amount, err := models.GetBigAmount(transactions[i].Splits[j].Amount)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tmp.Add(&pageDifference, rat_amount)
 | 
				
			||||||
 | 
									pageDifference.Set(&tmp)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &pageDifference, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tx *Tx) GetAccountTransactions(user *models.User, accountid int64, sort string, page uint64, limit uint64) (*models.AccountTransactionsList, error) {
 | 
				
			||||||
 | 
						var transactions []*models.Transaction
 | 
				
			||||||
 | 
						var atl models.AccountTransactionsList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var sqlsort, balanceLimitOffset string
 | 
				
			||||||
 | 
						var balanceLimitOffsetArg uint64
 | 
				
			||||||
 | 
						if sort == "date-asc" {
 | 
				
			||||||
 | 
							sqlsort = " ORDER BY transactions.Date ASC, transactions.TransactionId ASC"
 | 
				
			||||||
 | 
							balanceLimitOffset = " LIMIT ?"
 | 
				
			||||||
 | 
							balanceLimitOffsetArg = page * limit
 | 
				
			||||||
 | 
						} else if sort == "date-desc" {
 | 
				
			||||||
 | 
							numSplits, err := tx.SelectInt("SELECT count(*) FROM splits")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							sqlsort = " ORDER BY transactions.Date DESC, transactions.TransactionId DESC"
 | 
				
			||||||
 | 
							balanceLimitOffset = fmt.Sprintf(" LIMIT %d OFFSET ?", numSplits)
 | 
				
			||||||
 | 
							balanceLimitOffsetArg = (page + 1) * limit
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var sqloffset string
 | 
				
			||||||
 | 
						if page > 0 {
 | 
				
			||||||
 | 
							sqloffset = fmt.Sprintf(" OFFSET %d", page*limit)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						account, err := tx.GetAccount(accountid, user.UserId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						atl.Account = account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sql := "SELECT DISTINCT transactions.* FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + " LIMIT ?" + sqloffset
 | 
				
			||||||
 | 
						_, err = tx.Select(&transactions, sql, user.UserId, accountid, limit)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						atl.Transactions = &transactions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pageDifference, err := tx.transactionsBalanceDifference(accountid, transactions)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count, err := tx.SelectInt("SELECT count(DISTINCT transactions.TransactionId) FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?", user.UserId, accountid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						atl.TotalTransactions = count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						security, err := tx.GetSecurity(atl.Account.SecurityId, user.UserId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if security == nil {
 | 
				
			||||||
 | 
							return nil, errors.New("Security not found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Sum all the splits for all transaction splits for this account that
 | 
				
			||||||
 | 
						// occurred before the page we're returning
 | 
				
			||||||
 | 
						var amounts []string
 | 
				
			||||||
 | 
						sql = "SELECT s.Amount FROM splits AS s INNER JOIN (SELECT DISTINCT transactions.Date, transactions.TransactionId FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + balanceLimitOffset + ") as t ON s.TransactionId = t.TransactionId WHERE s.AccountId=?"
 | 
				
			||||||
 | 
						_, err = tx.Select(&amounts, sql, user.UserId, accountid, balanceLimitOffsetArg, accountid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var tmp, balance big.Rat
 | 
				
			||||||
 | 
						for _, amount := range amounts {
 | 
				
			||||||
 | 
							rat_amount, err := models.GetBigAmount(amount)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tmp.Add(&balance, rat_amount)
 | 
				
			||||||
 | 
							balance.Set(&tmp)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						atl.BeginningBalance = balance.FloatString(security.Precision)
 | 
				
			||||||
 | 
						atl.EndingBalance = tmp.Add(&balance, pageDifference).FloatString(security.Precision)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &atl, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -76,7 +76,23 @@ type AccountStore interface {
 | 
				
			|||||||
	DeleteAccount(account *models.Account) error
 | 
						DeleteAccount(account *models.Account) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AccountMissingError struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ame AccountMissingError) Error() string {
 | 
				
			||||||
 | 
						return "Account missing"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TransactionStore interface {
 | 
					type TransactionStore interface {
 | 
				
			||||||
 | 
						SplitExists(s *models.Split) (bool, error)
 | 
				
			||||||
 | 
						InsertTransaction(t *models.Transaction, user *models.User) error
 | 
				
			||||||
 | 
						GetTransaction(transactionid int64, userid int64) (*models.Transaction, error)
 | 
				
			||||||
 | 
						GetTransactions(userid int64) (*[]*models.Transaction, error)
 | 
				
			||||||
 | 
						UpdateTransaction(t *models.Transaction, user *models.User) error
 | 
				
			||||||
 | 
						DeleteTransaction(t *models.Transaction, user *models.User) error
 | 
				
			||||||
 | 
						GetAccountSplits(user *models.User, accountid int64) (*[]*models.Split, error)
 | 
				
			||||||
 | 
						GetAccountSplitsDate(user *models.User, accountid int64, date *time.Time) (*[]*models.Split, error)
 | 
				
			||||||
 | 
						GetAccountSplitsDateRange(user *models.User, accountid int64, begin, end *time.Time) (*[]*models.Split, error)
 | 
				
			||||||
 | 
						GetAccountTransactions(user *models.User, accountid int64, sort string, page uint64, limit uint64) (*models.AccountTransactionsList, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ReportStore interface {
 | 
					type ReportStore interface {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user