2016-02-02 21:46:27 -05:00
package main
import (
"io"
"log"
2016-02-12 05:53:03 -05:00
"math/big"
2016-02-02 21:46:27 -05:00
"net/http"
)
/ *
* Assumes the User is a valid , signed - in user , but accountid has not yet been validated
* /
2016-02-15 11:28:44 -05:00
func AccountImportHandler ( w http . ResponseWriter , r * http . Request , user * User , accountid int64 , importtype string ) {
//TODO branch off for different importtype's
2016-02-02 21:46:27 -05:00
multipartReader , err := r . MultipartReader ( )
if err != nil {
WriteError ( w , 3 /*Invalid Request*/ )
return
}
// assume there is only one 'part'
part , err := multipartReader . NextPart ( )
if err != nil {
if err == io . EOF {
WriteError ( w , 3 /*Invalid Request*/ )
} else {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
}
return
}
2017-05-08 06:01:26 -04:00
itl , err := ImportOFX ( part )
2016-02-02 21:46:27 -05:00
if err != nil {
2017-05-08 06:01:26 -04:00
//TODO is this necessarily an invalid request (what if it was an error on our end)?
WriteError ( w , 3 /*Invalid Request*/ )
2016-02-02 21:46:27 -05:00
log . Print ( err )
return
}
2017-05-08 06:01:26 -04:00
if len ( itl . Accounts ) != 1 {
WriteError ( w , 3 /*Invalid Request*/ )
log . Printf ( "Found %d accounts when importing OFX, expected 1" , len ( itl . Accounts ) )
return
}
sqltransaction , err := DB . Begin ( )
2016-02-02 21:46:27 -05:00
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2017-05-08 06:01:26 -04:00
// Return Account with this Id
account , err := GetAccountTx ( sqltransaction , accountid , user . UserId )
2016-02-02 21:46:27 -05:00
if err != nil {
2017-05-08 06:01:26 -04:00
sqltransaction . Rollback ( )
2016-02-02 21:46:27 -05:00
WriteError ( w , 3 /*Invalid Request*/ )
2017-05-08 06:01:26 -04:00
log . Print ( err )
2016-02-02 21:46:27 -05:00
return
}
2017-05-08 06:01:26 -04:00
importedAccount := itl . Accounts [ 0 ]
if len ( account . ExternalAccountId ) > 0 &&
account . ExternalAccountId != importedAccount . ExternalAccountId {
sqltransaction . Rollback ( )
WriteError ( w , 3 /*Invalid Request*/ )
log . Printf ( "OFX import has \"%s\" as ExternalAccountId, but the account being imported to has\"%s\"" ,
importedAccount . ExternalAccountId ,
account . ExternalAccountId )
return
}
if account . Type != importedAccount . Type {
sqltransaction . Rollback ( )
WriteError ( w , 3 /*Invalid Request*/ )
log . Printf ( "Expected %s account, found %s in OFX file" , account . Type . String ( ) , importedAccount . Type . String ( ) )
2016-02-15 11:28:44 -05:00
return
}
2017-05-08 06:01:26 -04:00
// Find matching existing securities or create new ones for those
// referenced by the OFX import. Also create a map from placeholder import
// SecurityIds to the actual SecurityIDs
var securitymap = make ( map [ int64 ] * Security )
for _ , ofxsecurity := range itl . Securities {
security , err := ImportGetCreateSecurity ( sqltransaction , user , & ofxsecurity )
if err != nil {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
securitymap [ ofxsecurity . SecurityId ] = security
}
if account . SecurityId != securitymap [ importedAccount . SecurityId ] . SecurityId {
sqltransaction . Rollback ( )
WriteError ( w , 3 /*Invalid Request*/ )
log . Printf ( "OFX import account's SecurityId (%d) does not match this account's (%d)" , securitymap [ importedAccount . SecurityId ] . SecurityId , account . SecurityId )
return
}
// TODO Ensure all transactions have at least one split in the account
// we're importing to?
2016-02-12 05:53:03 -05:00
var transactions [ ] Transaction
2017-05-08 06:01:26 -04:00
for _ , transaction := range itl . Transactions {
2016-02-12 05:53:03 -05:00
transaction . UserId = user . UserId
transaction . Status = Imported
2016-02-02 21:46:27 -05:00
if ! transaction . Valid ( ) {
2016-02-15 11:28:44 -05:00
sqltransaction . Rollback ( )
2017-05-08 06:01:26 -04:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( "Unexpected invalid transaction from OFX import" )
2016-02-02 21:46:27 -05:00
return
}
2017-05-08 06:01:26 -04:00
// Ensure that either AccountId or SecurityId is set for this split,
// and fixup the SecurityId to be a valid one for this user's actual
// securities instead of a placeholder from the import
for _ , split := range transaction . Splits {
if split . AccountId != - 1 {
if split . AccountId != importedAccount . AccountId {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
return
}
split . AccountId = account . AccountId
} else if split . SecurityId != - 1 {
if sec , ok := securitymap [ split . SecurityId ] ; ok {
split . SecurityId = sec . SecurityId
} else {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( "Couldn't find split's SecurityId in map during OFX import" )
return
}
} else {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( "Neither Split.AccountId Split.SecurityId was set during OFX import" )
return
}
}
2016-02-15 11:28:44 -05:00
imbalances , err := transaction . GetImbalancesTx ( sqltransaction )
2016-02-12 05:53:03 -05:00
if err != nil {
2016-02-15 11:28:44 -05:00
sqltransaction . Rollback ( )
2016-02-12 05:53:03 -05:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
// Fixup any imbalances in transactions
var zero big . Rat
var num_imbalances int
for _ , imbalance := range imbalances {
if imbalance . Cmp ( & zero ) != 0 {
num_imbalances += 1
}
}
for imbalanced_security , imbalance := range imbalances {
if imbalance . Cmp ( & zero ) != 0 {
var imbalanced_account * Account
// If we're dealing with exactly two securities, assume any imbalances
// from imports are from trading currencies/securities
if num_imbalances == 2 {
2016-02-15 11:28:44 -05:00
imbalanced_account , err = GetTradingAccount ( sqltransaction , user . UserId , imbalanced_security )
2016-02-12 05:53:03 -05:00
} else {
2016-02-15 11:28:44 -05:00
imbalanced_account , err = GetImbalanceAccount ( sqltransaction , user . UserId , imbalanced_security )
2016-02-12 05:53:03 -05:00
}
if err != nil {
2016-02-15 11:28:44 -05:00
sqltransaction . Rollback ( )
2016-02-12 05:53:03 -05:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
// Add new split to fixup imbalance
split := new ( Split )
r := new ( big . Rat )
r . Neg ( & imbalance )
2016-10-16 08:19:11 -04:00
security , err := GetSecurity ( imbalanced_security , user . UserId )
if err != nil {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2016-02-12 05:53:03 -05:00
split . Amount = r . FloatString ( security . Precision )
split . SecurityId = - 1
split . AccountId = imbalanced_account . AccountId
transaction . Splits = append ( transaction . Splits , split )
}
}
// Move any splits with SecurityId but not AccountId to Imbalances
// accounts
for _ , split := range transaction . Splits {
if split . SecurityId != - 1 || split . AccountId == - 1 {
2016-02-15 11:28:44 -05:00
imbalanced_account , err := GetImbalanceAccount ( sqltransaction , user . UserId , split . SecurityId )
2016-02-12 05:53:03 -05:00
if err != nil {
2016-02-15 11:28:44 -05:00
sqltransaction . Rollback ( )
2016-02-12 05:53:03 -05:00
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
split . AccountId = imbalanced_account . AccountId
split . SecurityId = - 1
}
}
transactions = append ( transactions , transaction )
2016-02-02 21:46:27 -05:00
}
2016-02-12 05:53:03 -05:00
for _ , transaction := range transactions {
2016-02-15 11:28:44 -05:00
err := InsertTransactionTx ( sqltransaction , & transaction , user )
2016-02-02 21:46:27 -05:00
if err != nil {
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
}
}
2016-02-15 11:28:44 -05:00
err = sqltransaction . Commit ( )
if err != nil {
sqltransaction . Rollback ( )
WriteError ( w , 999 /*Internal Error*/ )
log . Print ( err )
return
}
2016-02-02 21:46:27 -05:00
WriteSuccess ( w )
}