2017-10-04 19:35:59 -04:00
package handlers
2015-06-25 22:36:58 -04:00
import (
2015-06-28 23:03:34 -04:00
"encoding/json"
"errors"
2015-07-11 08:58:36 -04:00
"fmt"
"gopkg.in/gorp.v1"
2015-06-28 23:03:34 -04:00
"log"
2015-06-25 22:36:58 -04:00
"math/big"
2015-06-28 23:03:34 -04:00
"net/http"
2015-07-11 08:58:36 -04:00
"net/url"
"strconv"
2015-06-28 23:03:34 -04:00
"strings"
2015-06-25 22:36:58 -04:00
"time"
)
2017-09-20 20:09:40 -04:00
// Split.Status
2017-05-31 08:23:19 -04:00
const (
Imported int64 = 1
Entered = 2
Cleared = 3
Reconciled = 4
Voided = 5
)
2017-09-20 20:09:40 -04:00
// Split.ImportSplitType
const (
Default int64 = 0
2017-09-20 21:30:17 -04:00
ImportAccount = 1 // This split belongs to the main account being imported
SubAccount = 2 // This split belongs to a sub-account of that being imported
2017-09-20 20:09:40 -04:00
ExternalAccount = 3
TradingAccount = 4
Commission = 5
Taxes = 6
Fees = 7
Load = 8
IncomeAccount = 9
2017-09-20 21:30:17 -04:00
ExpenseAccount = 10
2017-09-20 20:09:40 -04:00
)
2015-06-25 22:36:58 -04:00
type Split struct {
2017-09-20 20:09:40 -04:00
SplitId int64
TransactionId int64
Status int64
ImportSplitType int64
2016-02-02 21:46:27 -05:00
// One of AccountId and SecurityId must be -1
// In normal splits, AccountId will be valid and SecurityId will be -1. The
// only case where this is reversed is for transactions that have been
// imported and not yet associated with an account.
AccountId int64
SecurityId int64
2017-06-09 05:37:42 -04:00
RemoteId string // unique ID from server, for detecting duplicates
Number string // Check or reference number
Memo string
Amount string // String representation of decimal, suitable for passing to big.Rat.SetString()
2015-06-25 22:36:58 -04:00
}
2015-08-21 06:54:17 -04:00
func GetBigAmount ( amt string ) ( * big . Rat , error ) {
2015-06-28 23:03:34 -04:00
var r big . Rat
2015-08-21 06:54:17 -04:00
_ , success := r . SetString ( amt )
2015-06-28 23:03:34 -04:00
if ! success {
2015-08-21 06:54:17 -04:00
return nil , errors . New ( "Couldn't convert string amount to big.Rat via SetString()" )
2015-06-28 23:03:34 -04:00
}
return & r , nil
}
2015-08-21 06:54:17 -04:00
func ( s * Split ) GetAmount ( ) ( * big . Rat , error ) {
return GetBigAmount ( s . Amount )
}
2015-06-28 23:03:34 -04:00
func ( s * Split ) Valid ( ) bool {
2017-05-08 06:01:26 -04:00
if ( s . AccountId == - 1 ) == ( s . SecurityId == - 1 ) {
2016-02-02 21:46:27 -05:00
return false
}
2015-06-28 23:03:34 -04:00
_ , err := s . GetAmount ( )
return err == nil
}
2017-06-10 15:22:13 -04:00
func ( s * Split ) AlreadyImportedTx ( transaction * gorp . Transaction ) ( bool , error ) {
count , err := transaction . SelectInt ( "SELECT COUNT(*) from splits where RemoteId=? and AccountId=?" , s . RemoteId , s . AccountId )
return count == 1 , err
}
2015-06-25 22:36:58 -04:00
type Transaction struct {
TransactionId int64
UserId int64
Description string
Date time . Time
2015-06-28 23:03:34 -04:00
Splits [ ] * Split ` db:"-" `
}
type TransactionList struct {
Transactions * [ ] Transaction ` json:"transactions" `
}
2015-07-11 08:58:36 -04:00
type AccountTransactionsList struct {
2015-08-21 06:54:17 -04:00
Account * Account
Transactions * [ ] Transaction
TotalTransactions int64
BeginningBalance string
EndingBalance string
2015-07-11 08:58:36 -04:00
}
2015-06-28 23:03:34 -04:00
func ( t * Transaction ) Write ( w http . ResponseWriter ) error {
enc := json . NewEncoder ( w )
return enc . Encode ( t )
}
func ( t * Transaction ) Read ( json_str string ) error {
dec := json . NewDecoder ( strings . NewReader ( json_str ) )
return dec . Decode ( t )
}
func ( tl * TransactionList ) Write ( w http . ResponseWriter ) error {
enc := json . NewEncoder ( w )
return enc . Encode ( tl )
}
2015-07-11 08:58:36 -04:00
func ( atl * AccountTransactionsList ) Write ( w http . ResponseWriter ) error {
enc := json . NewEncoder ( w )
return enc . Encode ( atl )
}
2015-06-28 23:03:34 -04:00
func ( t * Transaction ) Valid ( ) bool {
for i := range t . Splits {
if ! t . Splits [ i ] . Valid ( ) {
return false
}
}
return true
}
2016-02-11 06:08:05 -05:00
// Return a map of security ID's to big.Rat's containing the amount that
// security is imbalanced by
2016-02-15 11:28:44 -05:00
func ( t * Transaction ) GetImbalancesTx ( transaction * gorp . Transaction ) ( map [ int64 ] big . Rat , error ) {
2015-08-30 20:34:18 -04:00
sums := make ( map [ int64 ] big . Rat )
2015-06-28 23:03:34 -04:00
if ! t . Valid ( ) {
2016-02-11 06:08:05 -05:00
return nil , errors . New ( "Transaction invalid" )
2015-06-28 23:03:34 -04:00
}
2016-02-11 06:08:05 -05:00
2015-06-28 23:03:34 -04:00
for i := range t . Splits {
2016-02-02 21:46:27 -05:00
securityid := t . Splits [ i ] . SecurityId
if t . Splits [ i ] . AccountId != - 1 {
2016-02-15 11:28:44 -05:00
var err error
var account * Account
2017-10-04 08:05:51 -04:00
account , err = GetAccountTx ( transaction , t . Splits [ i ] . AccountId , t . UserId )
2016-02-02 21:46:27 -05:00
if err != nil {
2016-02-11 06:08:05 -05:00
return nil , err
2016-02-02 21:46:27 -05:00
}
securityid = account . SecurityId
2015-08-30 20:34:18 -04:00
}
2015-06-28 23:03:34 -04:00
amount , _ := t . Splits [ i ] . GetAmount ( )
2016-02-02 21:46:27 -05:00
sum := sums [ securityid ]
2015-08-30 20:34:18 -04:00
( & sum ) . Add ( & sum , amount )
2016-02-02 21:46:27 -05:00
sums [ securityid ] = sum
2015-08-30 20:34:18 -04:00
}
2016-02-11 06:08:05 -05:00
return sums , nil
}
// Returns true if all securities contained in this transaction are balanced,
// false otherwise
2017-10-04 08:05:51 -04:00
func ( t * Transaction ) Balanced ( transaction * gorp . Transaction ) ( bool , error ) {
2016-02-11 06:08:05 -05:00
var zero big . Rat
2017-10-04 08:05:51 -04:00
sums , err := t . GetImbalancesTx ( transaction )
2016-02-11 06:08:05 -05:00
if err != nil {
return false , err
}
2015-08-30 20:34:18 -04:00
for _ , security_sum := range sums {
if security_sum . Cmp ( & zero ) != 0 {
2015-08-30 20:41:47 -04:00
return false , nil
2015-06-28 23:03:34 -04:00
}
}
2015-08-30 20:41:47 -04:00
return true , nil
2015-06-28 23:03:34 -04:00
}
2017-10-04 08:05:51 -04:00
func GetTransaction ( db * DB , transactionid int64 , userid int64 ) ( * Transaction , error ) {
2015-06-28 23:03:34 -04:00
var t Transaction
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
2015-08-05 21:25:25 -04:00
err = transaction . SelectOne ( & t , "SELECT * from transactions where UserId=? AND TransactionId=?" , userid , transactionid )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
transaction . Rollback ( )
2015-06-28 23:03:34 -04:00
return nil , err
}
_ , err = transaction . Select ( & t . Splits , "SELECT * from splits where TransactionId=?" , transactionid )
if err != nil {
2017-10-04 08:05:51 -04:00
transaction . Rollback ( )
2015-06-28 23:03:34 -04:00
return nil , err
}
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
return & t , nil
}
2017-10-04 08:05:51 -04:00
func GetTransactions ( db * DB , userid int64 ) ( * [ ] Transaction , error ) {
2015-06-28 23:03:34 -04:00
var transactions [ ] Transaction
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
_ , err = transaction . Select ( & transactions , "SELECT * from transactions where UserId=?" , userid )
if err != nil {
return nil , err
}
for i := range transactions {
_ , err := transaction . Select ( & transactions [ i ] . Splits , "SELECT * from splits where TransactionId=?" , transactions [ i ] . TransactionId )
if err != nil {
return nil , err
}
}
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
return & transactions , nil
}
2015-07-11 08:58:36 -04:00
func incrementAccountVersions ( transaction * gorp . Transaction , user * User , accountids [ ] int64 ) error {
for i := range accountids {
account , err := GetAccountTx ( transaction , accountids [ i ] , user . UserId )
if err != nil {
return err
}
2015-08-05 21:25:25 -04:00
account . AccountVersion ++
2015-07-11 08:58:36 -04:00
count , err := transaction . Update ( account )
if err != nil {
return err
}
if count != 1 {
return errors . New ( "Updated more than one account" )
}
}
return nil
}
2015-06-28 23:03:34 -04:00
type AccountMissingError struct { }
func ( ame AccountMissingError ) Error ( ) string {
return "Account missing"
}
2016-02-15 11:28:44 -05:00
func InsertTransactionTx ( transaction * gorp . Transaction , t * Transaction , user * User ) error {
2015-07-11 08:58:36 -04:00
// Map of any accounts with transaction splits being added
a_map := make ( map [ int64 ] bool )
2015-06-28 23:03:34 -04:00
for i := range t . Splits {
2016-02-02 21:46:27 -05:00
if t . Splits [ i ] . AccountId != - 1 {
existing , err := transaction . 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 {
2015-06-28 23:03:34 -04:00
return AccountMissingError { }
}
2015-07-11 08:58:36 -04:00
}
//increment versions for all accounts
var a_ids [ ] int64
for id := range a_map {
a_ids = append ( a_ids , id )
}
2016-02-02 21:46:27 -05:00
// ensure at least one of the splits is associated with an actual account
if len ( a_ids ) < 1 {
return AccountMissingError { }
}
2016-02-15 11:28:44 -05:00
err := incrementAccountVersions ( transaction , user , a_ids )
2015-07-11 08:58:36 -04:00
if err != nil {
return err
2015-06-28 23:03:34 -04:00
}
2016-02-15 11:28:44 -05:00
t . UserId = user . UserId
2015-06-28 23:03:34 -04:00
err = transaction . Insert ( t )
if err != nil {
return err
}
for i := range t . Splits {
t . Splits [ i ] . TransactionId = t . TransactionId
t . Splits [ i ] . SplitId = - 1
err = transaction . Insert ( t . Splits [ i ] )
if err != nil {
return err
}
}
2016-02-15 11:28:44 -05:00
return nil
}
2016-10-16 08:19:11 -04:00
2017-10-04 08:05:51 -04:00
func InsertTransaction ( db * DB , t * Transaction , user * User ) error {
transaction , err := db . Begin ( )
2016-02-15 11:28:44 -05:00
if err != nil {
return err
}
err = InsertTransactionTx ( transaction , t , user )
if err != nil {
transaction . Rollback ( )
return err
}
2015-06-28 23:03:34 -04:00
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return err
}
return nil
}
2017-10-04 08:05:51 -04:00
func UpdateTransactionTx ( transaction * gorp . Transaction , t * Transaction , user * User ) error {
2015-06-28 23:03:34 -04:00
var existing_splits [ ] * Split
2017-10-04 08:05:51 -04:00
_ , err := transaction . Select ( & existing_splits , "SELECT * from splits where TransactionId=?" , t . TransactionId )
2015-06-28 23:03:34 -04:00
if err != nil {
return err
}
2015-07-11 08:58:36 -04:00
// Map of any accounts with transaction splits being added
a_map := make ( map [ int64 ] bool )
2015-06-28 23:03:34 -04:00
// Make a map with any existing splits for this transaction
2015-07-11 08:58:36 -04:00
s_map := make ( map [ int64 ] bool )
2015-06-28 23:03:34 -04:00
for i := range existing_splits {
2015-07-11 08:58:36 -04:00
s_map [ existing_splits [ i ] . SplitId ] = true
2015-06-28 23:03:34 -04:00
}
// Insert splits, updating any pre-existing ones
for i := range t . Splits {
t . Splits [ i ] . TransactionId = t . TransactionId
2015-07-11 08:58:36 -04:00
_ , ok := s_map [ t . Splits [ i ] . SplitId ]
2015-06-28 23:03:34 -04:00
if ok {
count , err := transaction . Update ( t . Splits [ i ] )
if err != nil {
return err
}
if count != 1 {
return errors . New ( "Updated more than one transaction split" )
}
2017-05-31 08:18:21 -04:00
delete ( s_map , t . Splits [ i ] . SplitId )
2015-06-28 23:03:34 -04:00
} else {
t . Splits [ i ] . SplitId = - 1
err := transaction . Insert ( t . Splits [ i ] )
if err != nil {
return err
}
}
2016-02-02 21:46:27 -05:00
if t . Splits [ i ] . AccountId != - 1 {
a_map [ t . Splits [ i ] . AccountId ] = true
}
2015-06-28 23:03:34 -04:00
}
// Delete any remaining pre-existing splits
for i := range existing_splits {
2015-07-11 08:58:36 -04:00
_ , ok := s_map [ existing_splits [ i ] . SplitId ]
2016-02-02 21:46:27 -05:00
if existing_splits [ i ] . AccountId != - 1 {
a_map [ existing_splits [ i ] . AccountId ] = true
}
2015-06-28 23:03:34 -04:00
if ok {
2015-07-11 08:58:36 -04:00
_ , err := transaction . Delete ( existing_splits [ i ] )
2015-06-28 23:03:34 -04:00
if err != nil {
return err
}
}
}
2015-07-11 08:58:36 -04:00
// 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 ( transaction , user , a_ids )
if err != nil {
return err
}
2015-06-28 23:03:34 -04:00
count , err := transaction . Update ( t )
if err != nil {
return err
}
if count != 1 {
return errors . New ( "Updated more than one transaction" )
}
return nil
}
2017-10-04 08:05:51 -04:00
func DeleteTransaction ( db * DB , t * Transaction , user * User ) error {
transaction , err := db . Begin ( )
2015-06-29 07:25:29 -04:00
if err != nil {
return err
}
2015-07-11 08:58:36 -04:00
var accountids [ ] int64
2016-02-02 21:46:27 -05:00
_ , err = transaction . Select ( & accountids , "SELECT DISTINCT AccountId FROM splits WHERE TransactionId=? AND AccountId != -1" , t . TransactionId )
2015-07-11 08:58:36 -04:00
if err != nil {
transaction . Rollback ( )
return err
}
_ , err = transaction . Exec ( "DELETE FROM splits WHERE TransactionId=?" , t . TransactionId )
2015-06-29 07:25:29 -04:00
if err != nil {
transaction . Rollback ( )
return err
}
count , err := transaction . Delete ( t )
if err != nil {
transaction . Rollback ( )
return err
}
if count != 1 {
transaction . Rollback ( )
return errors . New ( "Deleted more than one transaction" )
}
2015-07-11 08:58:36 -04:00
err = incrementAccountVersions ( transaction , user , accountids )
if err != nil {
transaction . Rollback ( )
return err
}
2015-06-29 07:25:29 -04:00
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return err
}
return nil
}
2017-10-04 08:05:51 -04:00
func TransactionHandler ( w http . ResponseWriter , r * http . Request , db * DB ) {
user , err := GetUserFromSession ( db , r )
2015-06-28 23:03:34 -04:00
if err != nil {
WriteError ( w , 1 /*Not Signed In*/ )
return
}
if r . Method == "POST" {
transaction_json := r . PostFormValue ( "transaction" )
if transaction_json == "" {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
var transaction Transaction
err := transaction . Read ( transaction_json )
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
transaction . TransactionId = - 1
transaction . UserId = user . UserId
2017-10-04 08:05:51 -04:00
sqltx , err := db . Begin ( )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
balanced , err := transaction . Balanced ( sqltx )
2015-08-30 20:41:47 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-08-30 20:41:47 -04:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
if ! transaction . Valid ( ) || ! balanced {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-06-28 23:03:34 -04:00
WriteError ( w , 3 /*Invalid Request*/ )
return
}
for i := range transaction . Splits {
transaction . Splits [ i ] . SplitId = - 1
2017-10-04 08:05:51 -04:00
_ , err := GetAccountTx ( sqltx , transaction . Splits [ i ] . AccountId , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-06-28 23:03:34 -04:00
WriteError ( w , 3 /*Invalid Request*/ )
return
}
}
2017-10-04 08:05:51 -04:00
err = InsertTransactionTx ( sqltx , & transaction , user )
2015-06-28 23:03:34 -04:00
if err != nil {
if _ , ok := err . ( AccountMissingError ) ; ok {
WriteError ( w , 3 /*Invalid Request*/ )
} else {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
}
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
return
}
err = sqltx . Commit ( )
if err != nil {
sqltx . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
2015-06-28 23:03:34 -04:00
return
}
2017-05-24 19:47:18 -04:00
err = transaction . Write ( w )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2015-06-28 23:03:34 -04:00
} else if r . Method == "GET" {
transactionid , err := GetURLID ( r . URL . Path )
2015-07-11 08:58:36 -04:00
2015-06-28 23:03:34 -04:00
if err != nil {
//Return all Transactions
var al TransactionList
2017-10-04 08:05:51 -04:00
transactions , err := GetTransactions ( db , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
al . Transactions = transactions
err = ( & al ) . Write ( w )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
} else {
//Return Transaction with this Id
2017-10-04 08:05:51 -04:00
transaction , err := GetTransaction ( db , transactionid , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
err = transaction . Write ( w )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
}
} else {
transactionid , err := GetURLID ( r . URL . Path )
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
if r . Method == "PUT" {
transaction_json := r . PostFormValue ( "transaction" )
if transaction_json == "" {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
var transaction Transaction
err := transaction . Read ( transaction_json )
if err != nil || transaction . TransactionId != transactionid {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
transaction . UserId = user . UserId
2017-10-04 08:05:51 -04:00
sqltx , err := db . Begin ( )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
balanced , err := transaction . Balanced ( sqltx )
2015-08-30 20:41:47 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-08-30 20:41:47 -04:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
if ! transaction . Valid ( ) || ! balanced {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-06-28 23:03:34 -04:00
WriteError ( w , 3 /*Invalid Request*/ )
return
}
for i := range transaction . Splits {
2017-10-04 08:05:51 -04:00
_ , err := GetAccountTx ( sqltx , transaction . Splits [ i ] . AccountId , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-06-28 23:03:34 -04:00
WriteError ( w , 3 /*Invalid Request*/ )
return
}
}
2017-10-04 08:05:51 -04:00
err = UpdateTransactionTx ( sqltx , & transaction , user )
if err != nil {
sqltx . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
err = sqltx . Commit ( )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-04 08:05:51 -04:00
sqltx . Rollback ( )
2015-06-28 23:03:34 -04:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2017-05-24 19:47:18 -04:00
err = transaction . Write ( w )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2015-06-28 23:03:34 -04:00
} else if r . Method == "DELETE" {
transactionid , err := GetURLID ( r . URL . Path )
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
2017-10-04 08:05:51 -04:00
transaction , err := GetTransaction ( db , transactionid , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
2017-10-04 08:05:51 -04:00
err = DeleteTransaction ( db , transaction , user )
2015-06-29 07:25:29 -04:00
if err != nil {
2015-06-28 23:03:34 -04:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
WriteSuccess ( w )
}
}
2015-06-25 22:36:58 -04:00
}
2015-07-11 08:58:36 -04:00
2017-01-27 21:50:02 -05:00
func TransactionsBalanceDifference ( transaction * gorp . Transaction , accountid int64 , transactions [ ] Transaction ) ( * big . Rat , error ) {
var pageDifference , tmp big . Rat
for i := range transactions {
_ , err := transaction . 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 := GetBigAmount ( transactions [ i ] . Splits [ j ] . Amount )
if err != nil {
return nil , err
}
tmp . Add ( & pageDifference , rat_amount )
pageDifference . Set ( & tmp )
}
}
}
return & pageDifference , nil
}
2017-10-04 08:05:51 -04:00
func GetAccountBalance ( db * DB , user * User , accountid int64 ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2017-01-27 21:50:02 -05:00
if err != nil {
return nil , err
}
2017-02-19 07:54:27 -05:00
sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=?"
_ , err = transaction . Select ( & splits , sql , accountid , user . UserId )
2017-01-27 21:50:02 -05:00
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
var balance , tmp big . Rat
for _ , s := range splits {
rat_amount , err := GetBigAmount ( s . Amount )
if err != nil {
transaction . Rollback ( )
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-27 21:50:02 -05:00
}
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
return & balance , nil
2017-01-27 21:50:02 -05:00
}
2017-02-19 07:54:27 -05:00
// Assumes accountid is valid and is owned by the current user
2017-10-04 08:05:51 -04:00
func GetAccountBalanceDate ( db * DB , user * User , accountid int64 , date * time . Time ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2017-01-30 21:04:18 -05:00
if err != nil {
return nil , err
}
2017-02-19 07:54:27 -05:00
sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date < ?"
_ , err = transaction . Select ( & splits , sql , accountid , user . UserId , date )
2017-01-30 21:04:18 -05:00
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
var balance , tmp big . Rat
for _ , s := range splits {
rat_amount , err := GetBigAmount ( s . Amount )
if err != nil {
transaction . Rollback ( )
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-30 21:04:18 -05:00
}
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
return & balance , nil
2017-01-30 21:04:18 -05:00
}
2017-10-04 08:05:51 -04:00
func GetAccountBalanceDateRange ( db * DB , user * User , accountid int64 , begin , end * time . Time ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2017-01-30 21:04:18 -05:00
if err != nil {
return nil , err
}
2017-02-19 07:54:27 -05:00
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 = transaction . Select ( & splits , sql , accountid , user . UserId , begin , end )
2017-01-30 21:04:18 -05:00
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
var balance , tmp big . Rat
for _ , s := range splits {
rat_amount , err := GetBigAmount ( s . Amount )
if err != nil {
transaction . Rollback ( )
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-30 21:04:18 -05:00
}
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-02-19 07:54:27 -05:00
return & balance , nil
2017-01-30 21:04:18 -05:00
}
2017-10-04 08:05:51 -04:00
func GetAccountTransactions ( db * DB , user * User , accountid int64 , sort string , page uint64 , limit uint64 ) ( * AccountTransactionsList , error ) {
2015-07-11 08:58:36 -04:00
var transactions [ ] Transaction
var atl AccountTransactionsList
2017-10-04 08:05:51 -04:00
transaction , err := db . Begin ( )
2015-08-22 09:46:11 -04:00
if err != nil {
return nil , err
}
var sqlsort , balanceLimitOffset string
var balanceLimitOffsetArg uint64
2015-07-11 08:58:36 -04:00
if sort == "date-asc" {
sqlsort = " ORDER BY transactions.Date ASC"
2015-08-22 09:46:11 -04:00
balanceLimitOffset = " LIMIT ?"
2015-08-30 19:43:26 -04:00
balanceLimitOffsetArg = page * limit
2015-07-11 08:58:36 -04:00
} else if sort == "date-desc" {
2015-08-22 09:46:11 -04:00
numSplits , err := transaction . SelectInt ( "SELECT count(*) FROM splits" )
if err != nil {
transaction . Rollback ( )
return nil , err
}
2015-07-11 08:58:36 -04:00
sqlsort = " ORDER BY transactions.Date DESC"
2015-08-22 09:46:11 -04:00
balanceLimitOffset = fmt . Sprintf ( " LIMIT %d OFFSET ?" , numSplits )
2015-08-30 19:43:26 -04:00
balanceLimitOffsetArg = ( page + 1 ) * limit
2015-07-11 08:58:36 -04:00
}
var sqloffset string
if page > 0 {
sqloffset = fmt . Sprintf ( " OFFSET %d" , page * limit )
}
account , err := GetAccountTx ( transaction , accountid , user . UserId )
if err != nil {
transaction . Rollback ( )
return nil , err
}
atl . Account = account
2015-08-08 09:05:36 -04:00
sql := "SELECT DISTINCT transactions.* FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + " LIMIT ?" + sqloffset
2015-07-11 08:58:36 -04:00
_ , err = transaction . Select ( & transactions , sql , user . UserId , accountid , limit )
if err != nil {
transaction . Rollback ( )
return nil , err
}
atl . Transactions = & transactions
2017-01-27 21:50:02 -05:00
pageDifference , err := TransactionsBalanceDifference ( transaction , accountid , transactions )
if err != nil {
transaction . Rollback ( )
return nil , err
2015-07-11 08:58:36 -04:00
}
2015-08-08 09:08:37 -04:00
count , err := transaction . 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 {
transaction . Rollback ( )
return nil , err
}
atl . TotalTransactions = count
2017-09-21 21:00:30 -04:00
security , err := GetSecurityTx ( transaction , atl . Account . SecurityId , user . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-09-21 21:00:30 -04:00
transaction . Rollback ( )
2016-10-16 08:19:11 -04:00
return nil , err
}
2015-08-21 06:54:17 -04:00
if security == nil {
2017-09-21 21:00:30 -04:00
transaction . Rollback ( )
2015-08-21 06:54:17 -04:00
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
2015-08-22 09:46:11 -04:00
sql = "SELECT splits.Amount FROM splits WHERE splits.AccountId=? AND splits.TransactionId IN (SELECT DISTINCT transactions.TransactionId FROM transactions INNER JOIN splits ON transactions.TransactionId = splits.TransactionId WHERE transactions.UserId=? AND splits.AccountId=?" + sqlsort + balanceLimitOffset + ")"
_ , err = transaction . Select ( & amounts , sql , accountid , user . UserId , accountid , balanceLimitOffsetArg )
2015-08-21 06:54:17 -04:00
if err != nil {
transaction . Rollback ( )
return nil , err
}
2017-01-27 21:50:02 -05:00
var tmp , balance big . Rat
2015-08-21 06:54:17 -04:00
for _ , amount := range amounts {
rat_amount , err := GetBigAmount ( amount )
if err != nil {
transaction . Rollback ( )
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
}
atl . BeginningBalance = balance . FloatString ( security . Precision )
2017-01-27 21:50:02 -05:00
atl . EndingBalance = tmp . Add ( & balance , pageDifference ) . FloatString ( security . Precision )
2015-08-21 06:54:17 -04:00
2015-07-11 08:58:36 -04:00
err = transaction . Commit ( )
if err != nil {
transaction . Rollback ( )
return nil , err
}
return & atl , nil
}
// Return only those transactions which have at least one split pertaining to
// an account
2017-10-04 08:05:51 -04:00
func AccountTransactionsHandler ( db * DB , w http . ResponseWriter , r * http . Request ,
2015-07-11 08:58:36 -04:00
user * User , accountid int64 ) {
var page uint64 = 0
var limit uint64 = 50
var sort string = "date-desc"
query , _ := url . ParseQuery ( r . URL . RawQuery )
pagestring := query . Get ( "page" )
if pagestring != "" {
p , err := strconv . ParseUint ( pagestring , 10 , 0 )
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
page = p
}
limitstring := query . Get ( "limit" )
if limitstring != "" {
l , err := strconv . ParseUint ( limitstring , 10 , 0 )
if err != nil || l > 100 {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
limit = l
}
sortstring := query . Get ( "sort" )
if sortstring != "" {
if sortstring != "date-asc" && sortstring != "date-desc" {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
sort = sortstring
}
2017-10-04 08:05:51 -04:00
accountTransactions , err := GetAccountTransactions ( db , user , accountid , sort , page , limit )
2015-07-11 08:58:36 -04:00
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
err = accountTransactions . Write ( w )
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
}