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"
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-10-14 19:41:13 -04:00
func ( s * Split ) AlreadyImported ( tx * Tx ) ( bool , error ) {
count , err := tx . SelectInt ( "SELECT COUNT(*) from splits where RemoteId=? and AccountId=?" , s . RemoteId , s . AccountId )
2017-06-10 15:22:13 -04:00
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 )
}
2017-10-23 21:14:19 -04:00
func ( tl * TransactionList ) Read ( json_str string ) error {
dec := json . NewDecoder ( strings . NewReader ( json_str ) )
return dec . Decode ( 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 )
}
2017-10-30 21:14:19 -04:00
func ( atl * AccountTransactionsList ) Read ( json_str string ) error {
dec := json . NewDecoder ( strings . NewReader ( json_str ) )
return dec . Decode ( 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
2017-10-14 19:41:13 -04:00
func ( t * Transaction ) GetImbalances ( tx * Tx ) ( 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-14 19:41:13 -04:00
account , err = GetAccount ( tx , 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-14 19:41:13 -04:00
func ( t * Transaction ) Balanced ( tx * Tx ) ( bool , error ) {
2016-02-11 06:08:05 -05:00
var zero big . Rat
2017-10-14 19:41:13 -04:00
sums , err := t . GetImbalances ( tx )
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-14 14:20:50 -04:00
func GetTransaction ( tx * Tx , transactionid int64 , userid int64 ) ( * Transaction , error ) {
2015-06-28 23:03:34 -04:00
var t Transaction
2017-10-14 14:20:50 -04:00
err := tx . SelectOne ( & t , "SELECT * from transactions where UserId=? AND TransactionId=?" , userid , transactionid )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
2017-10-14 14:20:50 -04:00
_ , err = tx . Select ( & t . Splits , "SELECT * from splits where TransactionId=?" , transactionid )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
return & t , nil
}
2017-10-14 14:20:50 -04:00
func GetTransactions ( tx * Tx , userid int64 ) ( * [ ] Transaction , error ) {
2015-06-28 23:03:34 -04:00
var transactions [ ] Transaction
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & transactions , "SELECT * from transactions where UserId=?" , userid )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
for i := range transactions {
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & transactions [ i ] . Splits , "SELECT * from splits where TransactionId=?" , transactions [ i ] . TransactionId )
2015-06-28 23:03:34 -04:00
if err != nil {
return nil , err
}
}
return & transactions , nil
}
2017-10-14 14:20:50 -04:00
func incrementAccountVersions ( tx * Tx , user * User , accountids [ ] int64 ) error {
2015-07-11 08:58:36 -04:00
for i := range accountids {
2017-10-14 19:41:13 -04:00
account , err := GetAccount ( tx , accountids [ i ] , user . UserId )
2015-07-11 08:58:36 -04:00
if err != nil {
return err
}
2015-08-05 21:25:25 -04:00
account . AccountVersion ++
2017-10-14 14:20:50 -04:00
count , err := tx . Update ( account )
2015-07-11 08:58:36 -04:00
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"
}
2017-10-14 19:41:13 -04:00
func InsertTransaction ( tx * Tx , 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 {
2017-10-14 14:20:50 -04:00
existing , err := tx . SelectInt ( "SELECT count(*) from accounts where AccountId=?" , t . Splits [ i ] . AccountId )
2016-02-02 21:46:27 -05:00
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 { }
}
2017-10-14 14:20:50 -04:00
err := incrementAccountVersions ( tx , 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
2017-10-14 14:20:50 -04:00
err = tx . Insert ( t )
2015-06-28 23:03:34 -04:00
if err != nil {
return err
}
for i := range t . Splits {
t . Splits [ i ] . TransactionId = t . TransactionId
t . Splits [ i ] . SplitId = - 1
2017-10-14 14:20:50 -04:00
err = tx . Insert ( t . Splits [ i ] )
2015-06-28 23:03:34 -04:00
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-14 19:41:13 -04:00
func UpdateTransaction ( tx * Tx , t * Transaction , user * User ) error {
2015-06-28 23:03:34 -04:00
var existing_splits [ ] * Split
2017-10-14 14:20:50 -04:00
_ , err := tx . 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 {
2017-10-14 14:20:50 -04:00
count , err := tx . Update ( t . Splits [ i ] )
2015-06-28 23:03:34 -04:00
if err != nil {
return err
}
2017-10-24 20:57:55 -04:00
if count > 1 {
return fmt . Errorf ( "Updated %d transaction splits while attempting to update only 1" , count )
2015-06-28 23:03:34 -04:00
}
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
2017-10-14 14:20:50 -04:00
err := tx . Insert ( t . Splits [ i ] )
2015-06-28 23:03:34 -04:00
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 {
2017-10-14 14:20:50 -04:00
_ , err := tx . 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 )
}
2017-10-14 14:20:50 -04:00
err = incrementAccountVersions ( tx , user , a_ids )
2015-07-11 08:58:36 -04:00
if err != nil {
return err
}
2017-10-14 14:20:50 -04:00
count , err := tx . Update ( t )
2015-06-28 23:03:34 -04:00
if err != nil {
return err
}
2017-10-24 20:57:55 -04:00
if count > 1 {
return fmt . Errorf ( "Updated %d transactions (expected 1)" , count )
2015-06-28 23:03:34 -04:00
}
return nil
}
2017-10-14 14:20:50 -04:00
func DeleteTransaction ( tx * Tx , t * Transaction , user * User ) error {
2015-07-11 08:58:36 -04:00
var accountids [ ] int64
2017-10-14 14:20:50 -04:00
_ , err := tx . 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 {
return err
}
2017-10-14 14:20:50 -04:00
_ , err = tx . Exec ( "DELETE FROM splits WHERE TransactionId=?" , t . TransactionId )
2015-06-29 07:25:29 -04:00
if err != nil {
return err
}
2017-10-14 14:20:50 -04:00
count , err := tx . Delete ( t )
2015-06-29 07:25:29 -04:00
if err != nil {
return err
}
if count != 1 {
return errors . New ( "Deleted more than one transaction" )
}
2017-10-14 14:20:50 -04:00
err = incrementAccountVersions ( tx , user , accountids )
2015-06-29 07:25:29 -04:00
if err != nil {
return err
}
return nil
}
2017-10-14 14:20:50 -04:00
func TransactionHandler ( r * http . Request , tx * Tx ) ResponseWriterWriter {
user , err := GetUserFromSession ( tx , r )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 1 /*Not Signed In*/ )
2015-06-28 23:03:34 -04:00
}
if r . Method == "POST" {
transaction_json := r . PostFormValue ( "transaction" )
if transaction_json == "" {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
var transaction Transaction
err := transaction . Read ( transaction_json )
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
transaction . TransactionId = - 1
transaction . UserId = user . UserId
2017-10-21 06:50:31 -04:00
if len ( transaction . Splits ) == 0 {
return NewError ( 3 /*Invalid Request*/ )
}
2015-06-28 23:03:34 -04:00
for i := range transaction . Splits {
transaction . Splits [ i ] . SplitId = - 1
2017-10-14 19:41:13 -04:00
_ , err := GetAccount ( tx , transaction . Splits [ i ] . AccountId , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
}
2017-10-16 05:39:41 -04:00
balanced , err := transaction . Balanced ( tx )
if err != nil {
return NewError ( 999 /*Internal Error*/ )
}
if ! transaction . Valid ( ) || ! balanced {
return NewError ( 3 /*Invalid Request*/ )
}
2017-10-14 19:41:13 -04:00
err = InsertTransaction ( tx , & transaction , user )
2015-06-28 23:03:34 -04:00
if err != nil {
if _ , ok := err . ( AccountMissingError ) ; ok {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
} else {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-04 08:05:51 -04:00
}
2017-10-14 14:20:50 -04:00
return & transaction
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-14 14:20:50 -04:00
transactions , err := GetTransactions ( tx , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-06-28 23:03:34 -04:00
}
al . Transactions = transactions
2017-10-14 14:20:50 -04:00
return & al
2015-06-28 23:03:34 -04:00
} else {
//Return Transaction with this Id
2017-10-14 14:20:50 -04:00
transaction , err := GetTransaction ( tx , transactionid , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-14 14:20:50 -04:00
return transaction
2015-06-28 23:03:34 -04:00
}
} else {
transactionid , err := GetURLID ( r . URL . Path )
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
if r . Method == "PUT" {
transaction_json := r . PostFormValue ( "transaction" )
if transaction_json == "" {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
var transaction Transaction
err := transaction . Read ( transaction_json )
if err != nil || transaction . TransactionId != transactionid {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
transaction . UserId = user . UserId
2017-10-14 14:20:50 -04:00
balanced , err := transaction . Balanced ( tx )
2015-08-30 20:41:47 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-08-30 20:41:47 -04:00
}
if ! transaction . Valid ( ) || ! balanced {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-21 06:50:31 -04:00
if len ( transaction . Splits ) == 0 {
return NewError ( 3 /*Invalid Request*/ )
}
2015-06-28 23:03:34 -04:00
for i := range transaction . Splits {
2017-10-14 19:41:13 -04:00
_ , err := GetAccount ( tx , transaction . Splits [ i ] . AccountId , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
}
2017-10-14 19:41:13 -04:00
err = UpdateTransaction ( tx , & transaction , user )
2017-10-04 08:05:51 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2017-10-04 08:05:51 -04:00
}
2017-10-14 14:20:50 -04:00
return & transaction
2015-06-28 23:03:34 -04:00
} else if r . Method == "DELETE" {
transactionid , err := GetURLID ( r . URL . Path )
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-14 14:20:50 -04:00
transaction , err := GetTransaction ( tx , transactionid , user . UserId )
2015-06-28 23:03:34 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-14 14:20:50 -04:00
err = DeleteTransaction ( tx , transaction , user )
2015-06-29 07:25:29 -04:00
if err != nil {
2015-06-28 23:03:34 -04:00
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-06-28 23:03:34 -04:00
}
2017-10-14 14:20:50 -04:00
return SuccessWriter { }
2015-06-28 23:03:34 -04:00
}
}
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-25 22:36:58 -04:00
}
2015-07-11 08:58:36 -04:00
2017-10-14 19:41:13 -04:00
func TransactionsBalanceDifference ( tx * Tx , accountid int64 , transactions [ ] Transaction ) ( * big . Rat , error ) {
2017-01-27 21:50:02 -05:00
var pageDifference , tmp big . Rat
for i := range transactions {
2017-10-14 19:41:13 -04:00
_ , err := tx . Select ( & transactions [ i ] . Splits , "SELECT * FROM splits where TransactionId=?" , transactions [ i ] . TransactionId )
2017-01-27 21:50:02 -05:00
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-14 14:20:50 -04:00
func GetAccountBalance ( tx * Tx , user * User , accountid int64 ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-01-27 21:50:02 -05:00
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=?"
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & splits , sql , accountid , user . UserId )
2017-01-27 21:50:02 -05:00
if err != nil {
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 {
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-27 21:50:02 -05:00
}
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-14 14:20:50 -04:00
func GetAccountBalanceDate ( tx * Tx , user * User , accountid int64 , date * time . Time ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-01-30 21:04:18 -05:00
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 < ?"
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & splits , sql , accountid , user . UserId , date )
2017-01-30 21:04:18 -05:00
if err != nil {
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 {
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-30 21:04:18 -05:00
}
2017-02-19 07:54:27 -05:00
return & balance , nil
2017-01-30 21:04:18 -05:00
}
2017-10-14 14:20:50 -04:00
func GetAccountBalanceDateRange ( tx * Tx , user * User , accountid int64 , begin , end * time . Time ) ( * big . Rat , error ) {
2017-02-19 07:54:27 -05:00
var splits [ ] Split
2017-01-30 21:04:18 -05:00
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 < ?"
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & splits , sql , accountid , user . UserId , begin , end )
2017-01-30 21:04:18 -05:00
if err != nil {
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 {
return nil , err
}
tmp . Add ( & balance , rat_amount )
balance . Set ( & tmp )
2017-01-30 21:04:18 -05:00
}
2017-02-19 07:54:27 -05:00
return & balance , nil
2017-01-30 21:04:18 -05:00
}
2017-10-14 14:20:50 -04:00
func GetAccountTransactions ( tx * Tx , 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
2015-08-22 09:46:11 -04:00
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" {
2017-10-14 14:20:50 -04:00
numSplits , err := tx . SelectInt ( "SELECT count(*) FROM splits" )
2015-08-22 09:46:11 -04:00
if err != nil {
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 )
}
2017-10-14 19:41:13 -04:00
account , err := GetAccount ( tx , accountid , user . UserId )
2015-07-11 08:58:36 -04:00
if err != nil {
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
2017-10-14 14:20:50 -04:00
_ , err = tx . Select ( & transactions , sql , user . UserId , accountid , limit )
2015-07-11 08:58:36 -04:00
if err != nil {
return nil , err
}
atl . Transactions = & transactions
2017-10-14 14:20:50 -04:00
pageDifference , err := TransactionsBalanceDifference ( tx , accountid , transactions )
2017-01-27 21:50:02 -05:00
if err != nil {
return nil , err
2015-07-11 08:58:36 -04:00
}
2017-10-14 14:20:50 -04:00
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 )
2015-08-08 09:08:37 -04:00
if err != nil {
return nil , err
}
atl . TotalTransactions = count
2017-10-14 19:41:13 -04:00
security , err := GetSecurity ( tx , atl . Account . SecurityId , user . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
return nil , err
}
2015-08-21 06:54:17 -04:00
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
2017-10-30 21:14:19 -04:00
sql = "SELECT s.Amount FROM splits AS s INNER JOIN (SELECT DISTINCT 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 )
2015-08-21 06:54:17 -04:00
if err != nil {
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 {
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
return & atl , nil
}
// Return only those transactions which have at least one split pertaining to
// an account
2017-10-14 14:20:50 -04:00
func AccountTransactionsHandler ( tx * Tx , r * http . Request , user * User , accountid int64 ) ResponseWriterWriter {
2015-07-11 08:58:36 -04:00
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 {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-07-11 08:58:36 -04:00
}
page = p
}
limitstring := query . Get ( "limit" )
if limitstring != "" {
l , err := strconv . ParseUint ( limitstring , 10 , 0 )
if err != nil || l > 100 {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-07-11 08:58:36 -04:00
}
limit = l
}
sortstring := query . Get ( "sort" )
if sortstring != "" {
if sortstring != "date-asc" && sortstring != "date-desc" {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-07-11 08:58:36 -04:00
}
sort = sortstring
}
2017-10-14 14:20:50 -04:00
accountTransactions , err := GetAccountTransactions ( tx , user , accountid , sort , page , limit )
2015-07-11 08:58:36 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-07-11 08:58:36 -04:00
}
2017-10-14 14:20:50 -04:00
return accountTransactions
2015-07-11 08:58:36 -04:00
}