mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-30 09:33:25 -04:00 
			
		
		
		
	Split accounts and transactions into models
This commit is contained in:
		| @@ -36,10 +36,10 @@ func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { | ||||
| 	dbmap := &gorp.DbMap{Db: db, Dialect: dialect} | ||||
| 	dbmap.AddTableWithName(models.User{}, "users").SetKeys(true, "UserId") | ||||
| 	dbmap.AddTableWithName(models.Session{}, "sessions").SetKeys(true, "SessionId") | ||||
| 	dbmap.AddTableWithName(handlers.Account{}, "accounts").SetKeys(true, "AccountId") | ||||
| 	dbmap.AddTableWithName(models.Account{}, "accounts").SetKeys(true, "AccountId") | ||||
| 	dbmap.AddTableWithName(models.Security{}, "securities").SetKeys(true, "SecurityId") | ||||
| 	dbmap.AddTableWithName(handlers.Transaction{}, "transactions").SetKeys(true, "TransactionId") | ||||
| 	dbmap.AddTableWithName(handlers.Split{}, "splits").SetKeys(true, "SplitId") | ||||
| 	dbmap.AddTableWithName(models.Transaction{}, "transactions").SetKeys(true, "TransactionId") | ||||
| 	dbmap.AddTableWithName(models.Split{}, "splits").SetKeys(true, "SplitId") | ||||
| 	dbmap.AddTableWithName(handlers.Price{}, "prices").SetKeys(true, "PriceId") | ||||
| 	rtable := dbmap.AddTableWithName(handlers.Report{}, "reports").SetKeys(true, "ReportId") | ||||
| 	rtable.ColMap("Lua").SetMaxSize(handlers.LuaMaxLength + luaMaxLengthBuffer) | ||||
|   | ||||
| @@ -1,127 +1,14 @@ | ||||
| package handlers | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type AccountType int64 | ||||
|  | ||||
| const ( | ||||
| 	Bank       AccountType = 1 // start at 1 so that the default (0) is invalid | ||||
| 	Cash                   = 2 | ||||
| 	Asset                  = 3 | ||||
| 	Liability              = 4 | ||||
| 	Investment             = 5 | ||||
| 	Income                 = 6 | ||||
| 	Expense                = 7 | ||||
| 	Trading                = 8 | ||||
| 	Equity                 = 9 | ||||
| 	Receivable             = 10 | ||||
| 	Payable                = 11 | ||||
| ) | ||||
|  | ||||
| var AccountTypes = []AccountType{ | ||||
| 	Bank, | ||||
| 	Cash, | ||||
| 	Asset, | ||||
| 	Liability, | ||||
| 	Investment, | ||||
| 	Income, | ||||
| 	Expense, | ||||
| 	Trading, | ||||
| 	Equity, | ||||
| 	Receivable, | ||||
| 	Payable, | ||||
| } | ||||
|  | ||||
| func (t AccountType) String() string { | ||||
| 	switch t { | ||||
| 	case Bank: | ||||
| 		return "Bank" | ||||
| 	case Cash: | ||||
| 		return "Cash" | ||||
| 	case Asset: | ||||
| 		return "Asset" | ||||
| 	case Liability: | ||||
| 		return "Liability" | ||||
| 	case Investment: | ||||
| 		return "Investment" | ||||
| 	case Income: | ||||
| 		return "Income" | ||||
| 	case Expense: | ||||
| 		return "Expense" | ||||
| 	case Trading: | ||||
| 		return "Trading" | ||||
| 	case Equity: | ||||
| 		return "Equity" | ||||
| 	case Receivable: | ||||
| 		return "Receivable" | ||||
| 	case Payable: | ||||
| 		return "Payable" | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type Account struct { | ||||
| 	AccountId         int64 | ||||
| 	ExternalAccountId string | ||||
| 	UserId            int64 | ||||
| 	SecurityId        int64 | ||||
| 	ParentAccountId   int64 // -1 if this account is at the root | ||||
| 	Type              AccountType | ||||
| 	Name              string | ||||
|  | ||||
| 	// monotonically-increasing account transaction version number. Used for | ||||
| 	// allowing a client to ensure they have a consistent version when paging | ||||
| 	// through transactions. | ||||
| 	AccountVersion int64 `json:"Version"` | ||||
|  | ||||
| 	// Optional fields specifying how to fetch transactions from a bank via OFX | ||||
| 	OFXURL       string | ||||
| 	OFXORG       string | ||||
| 	OFXFID       string | ||||
| 	OFXUser      string | ||||
| 	OFXBankID    string // OFX BankID (BrokerID if AcctType == Investment) | ||||
| 	OFXAcctID    string | ||||
| 	OFXAcctType  string // ofxgo.acctType | ||||
| 	OFXClientUID string | ||||
| 	OFXAppID     string | ||||
| 	OFXAppVer    string | ||||
| 	OFXVersion   string | ||||
| 	OFXNoIndent  bool | ||||
| } | ||||
|  | ||||
| type AccountList struct { | ||||
| 	Accounts *[]Account `json:"accounts"` | ||||
| } | ||||
|  | ||||
| func (a *Account) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(a) | ||||
| } | ||||
|  | ||||
| func (a *Account) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(a) | ||||
| } | ||||
|  | ||||
| func (al *AccountList) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(al) | ||||
| } | ||||
|  | ||||
| func (al *AccountList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(al) | ||||
| } | ||||
|  | ||||
| func GetAccount(tx *Tx, accountid int64, userid int64) (*Account, error) { | ||||
| 	var a Account | ||||
| func GetAccount(tx *Tx, accountid int64, userid int64) (*models.Account, error) { | ||||
| 	var a models.Account | ||||
|  | ||||
| 	err := tx.SelectOne(&a, "SELECT * from accounts where UserId=? AND AccountId=?", userid, accountid) | ||||
| 	if err != nil { | ||||
| @@ -130,8 +17,8 @@ func GetAccount(tx *Tx, accountid int64, userid int64) (*Account, error) { | ||||
| 	return &a, nil | ||||
| } | ||||
|  | ||||
| func GetAccounts(tx *Tx, userid int64) (*[]Account, error) { | ||||
| 	var accounts []Account | ||||
| func GetAccounts(tx *Tx, userid int64) (*[]models.Account, error) { | ||||
| 	var accounts []models.Account | ||||
|  | ||||
| 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=?", userid) | ||||
| 	if err != nil { | ||||
| @@ -142,9 +29,9 @@ func GetAccounts(tx *Tx, userid int64) (*[]Account, error) { | ||||
|  | ||||
| // Get (and attempt to create if it doesn't exist). Matches on UserId, | ||||
| // SecurityId, Type, Name, and ParentAccountId | ||||
| func GetCreateAccount(tx *Tx, a Account) (*Account, error) { | ||||
| 	var accounts []Account | ||||
| 	var account Account | ||||
| func GetCreateAccount(tx *Tx, a models.Account) (*models.Account, error) { | ||||
| 	var accounts []models.Account | ||||
| 	var account models.Account | ||||
|  | ||||
| 	// Try to find the top-level trading account | ||||
| 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=? AND SecurityId=? AND Type=? AND Name=? AND ParentAccountId=? ORDER BY AccountId ASC LIMIT 1", a.UserId, a.SecurityId, a.Type, a.Name, a.ParentAccountId) | ||||
| @@ -170,9 +57,9 @@ func GetCreateAccount(tx *Tx, a Account) (*Account, error) { | ||||
|  | ||||
| // Get (and attempt to create if it doesn't exist) the security/currency | ||||
| // trading account for the supplied security/currency | ||||
| func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) { | ||||
| 	var tradingAccount Account | ||||
| 	var account Account | ||||
| func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*models.Account, error) { | ||||
| 	var tradingAccount models.Account | ||||
| 	var account models.Account | ||||
|  | ||||
| 	user, err := GetUser(tx, userid) | ||||
| 	if err != nil { | ||||
| @@ -180,7 +67,7 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | ||||
| 	} | ||||
|  | ||||
| 	tradingAccount.UserId = userid | ||||
| 	tradingAccount.Type = Trading | ||||
| 	tradingAccount.Type = models.Trading | ||||
| 	tradingAccount.Name = "Trading" | ||||
| 	tradingAccount.SecurityId = user.DefaultCurrency | ||||
| 	tradingAccount.ParentAccountId = -1 | ||||
| @@ -200,7 +87,7 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | ||||
| 	account.Name = security.Name | ||||
| 	account.ParentAccountId = ta.AccountId | ||||
| 	account.SecurityId = securityid | ||||
| 	account.Type = Trading | ||||
| 	account.Type = models.Trading | ||||
|  | ||||
| 	a, err := GetCreateAccount(tx, account) | ||||
| 	if err != nil { | ||||
| @@ -212,9 +99,9 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | ||||
|  | ||||
| // Get (and attempt to create if it doesn't exist) the security/currency | ||||
| // imbalance account for the supplied security/currency | ||||
| func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, error) { | ||||
| 	var imbalanceAccount Account | ||||
| 	var account Account | ||||
| func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*models.Account, error) { | ||||
| 	var imbalanceAccount models.Account | ||||
| 	var account models.Account | ||||
| 	xxxtemplate := FindSecurityTemplate("XXX", models.Currency) | ||||
| 	if xxxtemplate == nil { | ||||
| 		return nil, errors.New("Couldn't find XXX security template") | ||||
| @@ -228,7 +115,7 @@ func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, erro | ||||
| 	imbalanceAccount.Name = "Imbalances" | ||||
| 	imbalanceAccount.ParentAccountId = -1 | ||||
| 	imbalanceAccount.SecurityId = xxxsecurity.SecurityId | ||||
| 	imbalanceAccount.Type = Bank | ||||
| 	imbalanceAccount.Type = models.Bank | ||||
|  | ||||
| 	// Find/create the top-level trading account | ||||
| 	ia, err := GetCreateAccount(tx, imbalanceAccount) | ||||
| @@ -245,7 +132,7 @@ func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, erro | ||||
| 	account.Name = security.Name | ||||
| 	account.ParentAccountId = ia.AccountId | ||||
| 	account.SecurityId = securityid | ||||
| 	account.Type = Bank | ||||
| 	account.Type = models.Bank | ||||
|  | ||||
| 	a, err := GetCreateAccount(tx, account) | ||||
| 	if err != nil { | ||||
| @@ -273,7 +160,7 @@ func (cae CircularAccountsError) Error() string { | ||||
| 	return "Would result in circular account relationship" | ||||
| } | ||||
|  | ||||
| func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | ||||
| func insertUpdateAccount(tx *Tx, a *models.Account, insert bool) error { | ||||
| 	found := make(map[int64]bool) | ||||
| 	if !insert { | ||||
| 		found[a.AccountId] = true | ||||
| @@ -286,7 +173,7 @@ func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | ||||
| 			return TooMuchNestingError{} | ||||
| 		} | ||||
|  | ||||
| 		var a Account | ||||
| 		var a models.Account | ||||
| 		err := tx.SelectOne(&a, "SELECT * from accounts where AccountId=?", parentid) | ||||
| 		if err != nil { | ||||
| 			return ParentAccountMissingError{} | ||||
| @@ -329,15 +216,15 @@ func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func InsertAccount(tx *Tx, a *Account) error { | ||||
| func InsertAccount(tx *Tx, a *models.Account) error { | ||||
| 	return insertUpdateAccount(tx, a, true) | ||||
| } | ||||
|  | ||||
| func UpdateAccount(tx *Tx, a *Account) error { | ||||
| func UpdateAccount(tx *Tx, a *models.Account) error { | ||||
| 	return insertUpdateAccount(tx, a, false) | ||||
| } | ||||
|  | ||||
| func DeleteAccount(tx *Tx, a *Account) error { | ||||
| func DeleteAccount(tx *Tx, a *models.Account) error { | ||||
| 	if a.ParentAccountId != -1 { | ||||
| 		// Re-parent splits to this account's parent account if this account isn't a root account | ||||
| 		_, err := tx.Exec("UPDATE splits SET AccountId=? WHERE AccountId=?", a.ParentAccountId, a.AccountId) | ||||
| @@ -384,7 +271,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | ||||
| 			return AccountImportHandler(context, r, user, accountid) | ||||
| 		} | ||||
|  | ||||
| 		var account Account | ||||
| 		var account models.Account | ||||
| 		if err := ReadJSON(r, &account); err != nil { | ||||
| 			return NewError(3 /*Invalid Request*/) | ||||
| 		} | ||||
| @@ -415,7 +302,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | ||||
| 	} else if r.Method == "GET" { | ||||
| 		if context.LastLevel() { | ||||
| 			//Return all Accounts | ||||
| 			var al AccountList | ||||
| 			var al models.AccountList | ||||
| 			accounts, err := GetAccounts(context.Tx, user.UserId) | ||||
| 			if err != nil { | ||||
| 				log.Print(err) | ||||
| @@ -447,7 +334,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | ||||
| 			return NewError(3 /*Invalid Request*/) | ||||
| 		} | ||||
| 		if r.Method == "PUT" { | ||||
| 			var account Account | ||||
| 			var account models.Account | ||||
| 			if err := ReadJSON(r, &account); err != nil || account.AccountId != accountid { | ||||
| 				return NewError(3 /*Invalid Request*/) | ||||
| 			} | ||||
|   | ||||
| @@ -11,8 +11,8 @@ import ( | ||||
|  | ||||
| const luaAccountTypeName = "account" | ||||
|  | ||||
| func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, error) { | ||||
| 	var account_map map[int64]*Account | ||||
| func luaContextGetAccounts(L *lua.LState) (map[int64]*models.Account, error) { | ||||
| 	var account_map map[int64]*models.Account | ||||
|  | ||||
| 	ctx := L.Context() | ||||
|  | ||||
| @@ -21,7 +21,7 @@ func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, error) { | ||||
| 		return nil, errors.New("Couldn't find tx in lua's Context") | ||||
| 	} | ||||
|  | ||||
| 	account_map, ok = ctx.Value(accountsContextKey).(map[int64]*Account) | ||||
| 	account_map, ok = ctx.Value(accountsContextKey).(map[int64]*models.Account) | ||||
| 	if !ok { | ||||
| 		user, ok := ctx.Value(userContextKey).(*models.User) | ||||
| 		if !ok { | ||||
| @@ -33,7 +33,7 @@ func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, error) { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		account_map = make(map[int64]*Account) | ||||
| 		account_map = make(map[int64]*models.Account) | ||||
| 		for i := range *accounts { | ||||
| 			account_map[(*accounts)[i].AccountId] = &(*accounts)[i] | ||||
| 		} | ||||
| @@ -69,7 +69,7 @@ func luaRegisterAccounts(L *lua.LState) { | ||||
| 	L.SetField(mt, "__eq", L.NewFunction(luaAccount__eq)) | ||||
| 	L.SetField(mt, "__metatable", lua.LString("protected")) | ||||
|  | ||||
| 	for _, accttype := range AccountTypes { | ||||
| 	for _, accttype := range models.AccountTypes { | ||||
| 		L.SetField(mt, accttype.String(), lua.LNumber(float64(accttype))) | ||||
| 	} | ||||
|  | ||||
| @@ -79,7 +79,7 @@ func luaRegisterAccounts(L *lua.LState) { | ||||
| 	L.SetGlobal("get_accounts", getAccountsFn) | ||||
| } | ||||
|  | ||||
| func AccountToLua(L *lua.LState, account *Account) *lua.LUserData { | ||||
| func AccountToLua(L *lua.LState, account *models.Account) *lua.LUserData { | ||||
| 	ud := L.NewUserData() | ||||
| 	ud.Value = account | ||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaAccountTypeName)) | ||||
| @@ -87,9 +87,9 @@ func AccountToLua(L *lua.LState, account *Account) *lua.LUserData { | ||||
| } | ||||
|  | ||||
| // Checks whether the first lua argument is a *LUserData with *Account and returns this *Account. | ||||
| func luaCheckAccount(L *lua.LState, n int) *Account { | ||||
| func luaCheckAccount(L *lua.LState, n int) *models.Account { | ||||
| 	ud := L.CheckUserData(n) | ||||
| 	if account, ok := ud.Value.(*Account); ok { | ||||
| 	if account, ok := ud.Value.(*models.Account); ok { | ||||
| 		return account | ||||
| 	} | ||||
| 	L.ArgError(n, "account expected") | ||||
|   | ||||
| @@ -2,19 +2,20 @@ package handlers_test | ||||
|  | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func createAccount(client *http.Client, account *handlers.Account) (*handlers.Account, error) { | ||||
| 	var a handlers.Account | ||||
| func createAccount(client *http.Client, account *models.Account) (*models.Account, error) { | ||||
| 	var a models.Account | ||||
| 	err := create(client, account, &a, "/v1/accounts/") | ||||
| 	return &a, err | ||||
| } | ||||
|  | ||||
| func getAccount(client *http.Client, accountid int64) (*handlers.Account, error) { | ||||
| 	var a handlers.Account | ||||
| func getAccount(client *http.Client, accountid int64) (*models.Account, error) { | ||||
| 	var a models.Account | ||||
| 	err := read(client, &a, "/v1/accounts/"+strconv.FormatInt(accountid, 10)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -22,8 +23,8 @@ func getAccount(client *http.Client, accountid int64) (*handlers.Account, error) | ||||
| 	return &a, nil | ||||
| } | ||||
|  | ||||
| func getAccounts(client *http.Client) (*handlers.AccountList, error) { | ||||
| 	var al handlers.AccountList | ||||
| func getAccounts(client *http.Client) (*models.AccountList, error) { | ||||
| 	var al models.AccountList | ||||
| 	err := read(client, &al, "/v1/accounts/") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -31,8 +32,8 @@ func getAccounts(client *http.Client) (*handlers.AccountList, error) { | ||||
| 	return &al, nil | ||||
| } | ||||
|  | ||||
| func updateAccount(client *http.Client, account *handlers.Account) (*handlers.Account, error) { | ||||
| 	var a handlers.Account | ||||
| func updateAccount(client *http.Client, account *models.Account) (*models.Account, error) { | ||||
| 	var a models.Account | ||||
| 	err := update(client, account, &a, "/v1/accounts/"+strconv.FormatInt(account.AccountId, 10)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -40,7 +41,7 @@ func updateAccount(client *http.Client, account *handlers.Account) (*handlers.Ac | ||||
| 	return &a, nil | ||||
| } | ||||
|  | ||||
| func deleteAccount(client *http.Client, a *handlers.Account) error { | ||||
| func deleteAccount(client *http.Client, a *models.Account) error { | ||||
| 	err := remove(client, "/v1/accounts/"+strconv.FormatInt(a.AccountId, 10)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -137,7 +138,7 @@ func TestUpdateAccount(t *testing.T) { | ||||
| 			curr := d.accounts[i] | ||||
|  | ||||
| 			curr.Name = "blah" | ||||
| 			curr.Type = handlers.Payable | ||||
| 			curr.Type = models.Payable | ||||
| 			for _, s := range d.securities { | ||||
| 				if s.UserId == curr.UserId { | ||||
| 					curr.SecurityId = s.SecurityId | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/config" | ||||
| 	"github.com/aclindsa/moneygo/internal/db" | ||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| @@ -202,7 +203,7 @@ func uploadFile(client *http.Client, filename, urlsuffix string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func accountBalanceHelper(t *testing.T, client *http.Client, account *handlers.Account, balance string) { | ||||
| func accountBalanceHelper(t *testing.T, client *http.Client, account *models.Account, balance string) { | ||||
| 	t.Helper() | ||||
| 	transactions, err := getAccountTransactions(client, account.AccountId, 0, 0, "") | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -127,8 +127,8 @@ type GnucashXMLImport struct { | ||||
|  | ||||
| type GnucashImport struct { | ||||
| 	Securities   []models.Security | ||||
| 	Accounts     []Account | ||||
| 	Transactions []Transaction | ||||
| 	Accounts     []models.Account | ||||
| 	Transactions []models.Transaction | ||||
| 	Prices       []Price | ||||
| } | ||||
|  | ||||
| @@ -206,7 +206,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | ||||
| 	//Translate to our account format, figuring out parent relationships | ||||
| 	for guid := range accountMap { | ||||
| 		ga := accountMap[guid] | ||||
| 		var a Account | ||||
| 		var a models.Account | ||||
|  | ||||
| 		a.AccountId = ga.accountid | ||||
| 		if ga.ParentAccountId == rootAccount.AccountId { | ||||
| @@ -229,29 +229,29 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | ||||
| 		//TODO find account types | ||||
| 		switch ga.Type { | ||||
| 		default: | ||||
| 			a.Type = Bank | ||||
| 			a.Type = models.Bank | ||||
| 		case "ASSET": | ||||
| 			a.Type = Asset | ||||
| 			a.Type = models.Asset | ||||
| 		case "BANK": | ||||
| 			a.Type = Bank | ||||
| 			a.Type = models.Bank | ||||
| 		case "CASH": | ||||
| 			a.Type = Cash | ||||
| 			a.Type = models.Cash | ||||
| 		case "CREDIT", "LIABILITY": | ||||
| 			a.Type = Liability | ||||
| 			a.Type = models.Liability | ||||
| 		case "EQUITY": | ||||
| 			a.Type = Equity | ||||
| 			a.Type = models.Equity | ||||
| 		case "EXPENSE": | ||||
| 			a.Type = Expense | ||||
| 			a.Type = models.Expense | ||||
| 		case "INCOME": | ||||
| 			a.Type = Income | ||||
| 			a.Type = models.Income | ||||
| 		case "PAYABLE": | ||||
| 			a.Type = Payable | ||||
| 			a.Type = models.Payable | ||||
| 		case "RECEIVABLE": | ||||
| 			a.Type = Receivable | ||||
| 			a.Type = models.Receivable | ||||
| 		case "MUTUAL", "STOCK": | ||||
| 			a.Type = Investment | ||||
| 			a.Type = models.Investment | ||||
| 		case "TRADING": | ||||
| 			a.Type = Trading | ||||
| 			a.Type = models.Trading | ||||
| 		} | ||||
|  | ||||
| 		gncimport.Accounts = append(gncimport.Accounts, a) | ||||
| @@ -261,20 +261,20 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | ||||
| 	for i := range gncxml.Transactions { | ||||
| 		gt := gncxml.Transactions[i] | ||||
|  | ||||
| 		t := new(Transaction) | ||||
| 		t := new(models.Transaction) | ||||
| 		t.Description = gt.Description | ||||
| 		t.Date = gt.DatePosted.Date.Time | ||||
| 		for j := range gt.Splits { | ||||
| 			gs := gt.Splits[j] | ||||
| 			s := new(Split) | ||||
| 			s := new(models.Split) | ||||
|  | ||||
| 			switch gs.Status { | ||||
| 			default: // 'n', or not present | ||||
| 				s.Status = Imported | ||||
| 				s.Status = models.Imported | ||||
| 			case "c": | ||||
| 				s.Status = Cleared | ||||
| 				s.Status = models.Cleared | ||||
| 			case "y": | ||||
| 				s.Status = Reconciled | ||||
| 				s.Status = models.Reconciled | ||||
| 			} | ||||
|  | ||||
| 			account, ok := accountMap[gs.AccountId] | ||||
| @@ -437,7 +437,7 @@ func GnucashImportHandler(r *http.Request, context *Context) ResponseWriterWrite | ||||
| 			} | ||||
| 			split.AccountId = acctId | ||||
|  | ||||
| 			exists, err := split.AlreadyImported(context.Tx) | ||||
| 			exists, err := SplitAlreadyImported(context.Tx, split) | ||||
| 			if err != nil { | ||||
| 				log.Print("Error checking if split was already imported:", err) | ||||
| 				return NewError(999 /*Internal Error*/) | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package handlers_test | ||||
|  | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| @@ -32,19 +31,19 @@ func TestImportGnucash(t *testing.T) { | ||||
| 		} | ||||
|  | ||||
| 		// Next, find the Expenses/Groceries account and verify it's balance | ||||
| 		var income, equity, liabilities, expenses, salary, creditcard, groceries, cable, openingbalances *handlers.Account | ||||
| 		var income, equity, liabilities, expenses, salary, creditcard, groceries, cable, openingbalances *models.Account | ||||
| 		accounts, err := getAccounts(d.clients[0]) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error fetching accounts: %s\n", err) | ||||
| 		} | ||||
| 		for i, account := range *accounts.Accounts { | ||||
| 			if account.Name == "Income" && account.Type == handlers.Income && account.ParentAccountId == -1 { | ||||
| 			if account.Name == "Income" && account.Type == models.Income && account.ParentAccountId == -1 { | ||||
| 				income = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Equity" && account.Type == handlers.Equity && account.ParentAccountId == -1 { | ||||
| 			} else if account.Name == "Equity" && account.Type == models.Equity && account.ParentAccountId == -1 { | ||||
| 				equity = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Liabilities" && account.Type == handlers.Liability && account.ParentAccountId == -1 { | ||||
| 			} else if account.Name == "Liabilities" && account.Type == models.Liability && account.ParentAccountId == -1 { | ||||
| 				liabilities = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Expenses" && account.Type == handlers.Expense && account.ParentAccountId == -1 { | ||||
| 			} else if account.Name == "Expenses" && account.Type == models.Expense && account.ParentAccountId == -1 { | ||||
| 				expenses = &(*accounts.Accounts)[i] | ||||
| 			} | ||||
| 		} | ||||
| @@ -61,15 +60,15 @@ func TestImportGnucash(t *testing.T) { | ||||
| 			t.Fatalf("Couldn't find 'Expenses' account") | ||||
| 		} | ||||
| 		for i, account := range *accounts.Accounts { | ||||
| 			if account.Name == "Salary" && account.Type == handlers.Income && account.ParentAccountId == income.AccountId { | ||||
| 			if account.Name == "Salary" && account.Type == models.Income && account.ParentAccountId == income.AccountId { | ||||
| 				salary = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Opening Balances" && account.Type == handlers.Equity && account.ParentAccountId == equity.AccountId { | ||||
| 			} else if account.Name == "Opening Balances" && account.Type == models.Equity && account.ParentAccountId == equity.AccountId { | ||||
| 				openingbalances = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Credit Card" && account.Type == handlers.Liability && account.ParentAccountId == liabilities.AccountId { | ||||
| 			} else if account.Name == "Credit Card" && account.Type == models.Liability && account.ParentAccountId == liabilities.AccountId { | ||||
| 				creditcard = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Groceries" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { | ||||
| 			} else if account.Name == "Groceries" && account.Type == models.Expense && account.ParentAccountId == expenses.AccountId { | ||||
| 				groceries = &(*accounts.Accounts)[i] | ||||
| 			} else if account.Name == "Cable" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { | ||||
| 			} else if account.Name == "Cable" && account.Type == models.Expense && account.ParentAccountId == expenses.AccountId { | ||||
| 				cable = &(*accounts.Accounts)[i] | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -78,7 +78,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 	// TODO Ensure all transactions have at least one split in the account | ||||
| 	// we're importing to? | ||||
|  | ||||
| 	var transactions []Transaction | ||||
| 	var transactions []models.Transaction | ||||
| 	for _, transaction := range itl.Transactions { | ||||
| 		transaction.UserId = user.UserId | ||||
|  | ||||
| @@ -91,7 +91,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 		// 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 { | ||||
| 			split.Status = Imported | ||||
| 			split.Status = models.Imported | ||||
| 			if split.AccountId != -1 { | ||||
| 				if split.AccountId != importedAccount.AccountId { | ||||
| 					log.Print("Imported split's AccountId wasn't -1 but also didn't match the account") | ||||
| @@ -101,7 +101,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 			} else if split.SecurityId != -1 { | ||||
| 				if sec, ok := securitymap[split.SecurityId]; ok { | ||||
| 					// TODO try to auto-match splits to existing accounts based on past transactions that look like this one | ||||
| 					if split.ImportSplitType == TradingAccount { | ||||
| 					if split.ImportSplitType == models.TradingAccount { | ||||
| 						// Find/make trading account if we're that type of split | ||||
| 						trading_account, err := GetTradingAccount(tx, user.UserId, sec.SecurityId) | ||||
| 						if err != nil { | ||||
| @@ -110,8 +110,8 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 						} | ||||
| 						split.AccountId = trading_account.AccountId | ||||
| 						split.SecurityId = -1 | ||||
| 					} else if split.ImportSplitType == SubAccount { | ||||
| 						subaccount := &Account{ | ||||
| 					} else if split.ImportSplitType == models.SubAccount { | ||||
| 						subaccount := &models.Account{ | ||||
| 							UserId:          user.UserId, | ||||
| 							Name:            sec.Name, | ||||
| 							ParentAccountId: account.AccountId, | ||||
| @@ -138,7 +138,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		imbalances, err := transaction.GetImbalances(tx) | ||||
| 		imbalances, err := GetTransactionImbalances(tx, &transaction) | ||||
| 		if err != nil { | ||||
| 			log.Print(err) | ||||
| 			return NewError(999 /*Internal Error*/) | ||||
| @@ -155,7 +155,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 				} | ||||
|  | ||||
| 				// Add new split to fixup imbalance | ||||
| 				split := new(Split) | ||||
| 				split := new(models.Split) | ||||
| 				r := new(big.Rat) | ||||
| 				r.Neg(&imbalance) | ||||
| 				security, err := GetSecurity(tx, imbalanced_security, user.UserId) | ||||
| @@ -186,7 +186,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re | ||||
| 				split.SecurityId = -1 | ||||
| 			} | ||||
|  | ||||
| 			exists, err := split.AlreadyImported(tx) | ||||
| 			exists, err := SplitAlreadyImported(tx, split) | ||||
| 			if err != nil { | ||||
| 				log.Print("Error checking if split was already imported:", err) | ||||
| 				return NewError(999 /*Internal Error*/) | ||||
| @@ -251,7 +251,7 @@ func OFXImportHandler(context *Context, r *http.Request, user *models.User, acco | ||||
| 		return NewError(999 /*Internal Error*/) | ||||
| 	} | ||||
|  | ||||
| 	if account.Type == Investment { | ||||
| 	if account.Type == models.Investment { | ||||
| 		// Investment account | ||||
| 		statementRequest := ofxgo.InvStatementRequest{ | ||||
| 			TrnUID: *transactionuid, | ||||
|   | ||||
| @@ -11,8 +11,8 @@ import ( | ||||
|  | ||||
| type OFXImport struct { | ||||
| 	Securities   []models.Security | ||||
| 	Accounts     []Account | ||||
| 	Transactions []Transaction | ||||
| 	Accounts     []models.Account | ||||
| 	Transactions []models.Transaction | ||||
| 	//	Balances     map[int64]string // map AccountIDs to ending balances | ||||
| } | ||||
|  | ||||
| @@ -51,8 +51,8 @@ func (i *OFXImport) GetAddCurrency(isoname string) (*models.Security, error) { | ||||
| 	return &security, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) error { | ||||
| 	var t Transaction | ||||
| func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *models.Account) error { | ||||
| 	var t models.Transaction | ||||
|  | ||||
| 	t.Date = tran.DtPosted.UTC() | ||||
|  | ||||
| @@ -70,7 +70,7 @@ func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) er | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var s1, s2 Split | ||||
| 	var s1, s2 models.Split | ||||
| 	if len(tran.ExtdName) > 0 { | ||||
| 		s1.Memo = tran.ExtdName.String() | ||||
| 	} | ||||
| @@ -94,15 +94,15 @@ func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) er | ||||
| 	s1.RemoteId = "ofx:" + tran.FiTID.String() | ||||
| 	// TODO CorrectFiTID/CorrectAction? | ||||
|  | ||||
| 	s1.ImportSplitType = ImportAccount | ||||
| 	s2.ImportSplitType = ExternalAccount | ||||
| 	s1.ImportSplitType = models.ImportAccount | ||||
| 	s2.ImportSplitType = models.ExternalAccount | ||||
|  | ||||
| 	security := i.Securities[account.SecurityId-1] | ||||
| 	s1.Amount = amt.FloatString(security.Precision) | ||||
| 	s2.Amount = amt.Neg(amt).FloatString(security.Precision) | ||||
|  | ||||
| 	s1.Status = Imported | ||||
| 	s2.Status = Imported | ||||
| 	s1.Status = models.Imported | ||||
| 	s2.Status = models.Imported | ||||
|  | ||||
| 	s1.AccountId = account.AccountId | ||||
| 	s2.AccountId = -1 | ||||
| @@ -122,12 +122,12 @@ func (i *OFXImport) importOFXBank(stmt *ofxgo.StatementResponse) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	account := Account{ | ||||
| 	account := models.Account{ | ||||
| 		AccountId:         int64(len(i.Accounts) + 1), | ||||
| 		ExternalAccountId: stmt.BankAcctFrom.AcctID.String(), | ||||
| 		SecurityId:        security.SecurityId, | ||||
| 		ParentAccountId:   -1, | ||||
| 		Type:              Bank, | ||||
| 		Type:              models.Bank, | ||||
| 	} | ||||
|  | ||||
| 	if stmt.BankTranList != nil { | ||||
| @@ -149,12 +149,12 @@ func (i *OFXImport) importOFXCC(stmt *ofxgo.CCStatementResponse) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	account := Account{ | ||||
| 	account := models.Account{ | ||||
| 		AccountId:         int64(len(i.Accounts) + 1), | ||||
| 		ExternalAccountId: stmt.CCAcctFrom.AcctID.String(), | ||||
| 		SecurityId:        security.SecurityId, | ||||
| 		ParentAccountId:   -1, | ||||
| 		Type:              Liability, | ||||
| 		Type:              models.Liability, | ||||
| 	} | ||||
| 	i.Accounts = append(i.Accounts, account) | ||||
|  | ||||
| @@ -208,14 +208,14 @@ func (i *OFXImport) importSecurities(seclist *ofxgo.SecurityList) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) Transaction { | ||||
| 	var t Transaction | ||||
| func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) models.Transaction { | ||||
| 	var t models.Transaction | ||||
| 	t.Description = string(invtran.Memo) | ||||
| 	t.Date = invtran.DtTrade.UTC() | ||||
| 	return t | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&buy.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(buy.SecID.UniqueID), models.Stock) | ||||
| @@ -254,10 +254,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -266,10 +266,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -278,10 +278,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -290,10 +290,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -301,20 +301,20 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -324,10 +324,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	units.Abs(&buy.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -335,10 +335,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| @@ -349,7 +349,7 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, ac | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&income.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(income.SecID.UniqueID), models.Stock) | ||||
| @@ -370,10 +370,10 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, | ||||
| 		total.Mul(&total, &income.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||
| @@ -381,10 +381,10 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||
| @@ -395,7 +395,7 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&expense.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(expense.SecID.UniqueID), models.Stock) | ||||
| @@ -415,10 +415,10 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models. | ||||
| 		total.Mul(&total, &expense.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | ||||
| @@ -426,10 +426,10 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models. | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ExpenseAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ExpenseAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | ||||
| @@ -440,7 +440,7 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models. | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&marginint.InvTran) | ||||
|  | ||||
| 	memo := string(marginint.InvTran.Memo) | ||||
| @@ -454,10 +454,10 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde | ||||
| 		total.Mul(&total, &marginint.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | ||||
| @@ -465,10 +465,10 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | ||||
| @@ -479,7 +479,7 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&reinvest.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(reinvest.SecID.UniqueID), models.Stock) | ||||
| @@ -518,10 +518,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -530,10 +530,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -542,10 +542,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -554,10 +554,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -565,10 +565,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -576,10 +576,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -587,20 +587,20 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -610,10 +610,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	units.Abs(&reinvest.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -621,10 +621,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| @@ -635,7 +635,7 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Sec | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&retofcap.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(retofcap.SecID.UniqueID), models.Stock) | ||||
| @@ -655,10 +655,10 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Sec | ||||
| 		total.Mul(&total, &retofcap.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | ||||
| @@ -666,10 +666,10 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Sec | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | ||||
| @@ -680,7 +680,7 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Sec | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&sell.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(sell.SecID.UniqueID), models.Stock) | ||||
| @@ -722,10 +722,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -734,10 +734,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -746,10 +746,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -758,10 +758,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 		t.Splits = append(t.Splits, &models.Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			Status:          models.Imported, | ||||
| 			ImportSplitType: models.Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -769,20 +769,20 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -792,10 +792,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	units.Abs(&sell.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -803,10 +803,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| @@ -817,7 +817,7 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) (*Transaction, error) { | ||||
| func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *models.Account) (*models.Transaction, error) { | ||||
| 	t := i.GetInvTran(&transfer.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(transfer.SecID.UniqueID), models.Stock) | ||||
| @@ -834,10 +834,10 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) | ||||
| 		units.Neg(&transfer.Units.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||
| @@ -845,10 +845,10 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 	t.Splits = append(t.Splits, &models.Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ExternalAccount, | ||||
| 		Status:          models.Imported, | ||||
| 		ImportSplitType: models.ExternalAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||
| @@ -859,12 +859,12 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) AddInvTransaction(invtran *ofxgo.InvTransaction, account *Account, curdef *models.Security) error { | ||||
| func (i *OFXImport) AddInvTransaction(invtran *ofxgo.InvTransaction, account *models.Account, curdef *models.Security) error { | ||||
| 	if curdef.SecurityId < 1 || curdef.SecurityId > int64(len(i.Securities)) { | ||||
| 		return errors.New("Internal error: security index not found in OFX import\n") | ||||
| 	} | ||||
|  | ||||
| 	var t *Transaction | ||||
| 	var t *models.Transaction | ||||
| 	var err error | ||||
| 	if tran, ok := (*invtran).(ofxgo.BuyDebt); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| @@ -926,12 +926,12 @@ func (i *OFXImport) importOFXInv(stmt *ofxgo.InvStatementResponse) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	account := Account{ | ||||
| 	account := models.Account{ | ||||
| 		AccountId:         int64(len(i.Accounts) + 1), | ||||
| 		ExternalAccountId: stmt.InvAcctFrom.AcctID.String(), | ||||
| 		SecurityId:        security.SecurityId, | ||||
| 		ParentAccountId:   -1, | ||||
| 		Type:              Investment, | ||||
| 		Type:              models.Investment, | ||||
| 	} | ||||
| 	i.Accounts = append(i.Accounts, account) | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package handlers_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| @@ -77,7 +76,7 @@ func findSecurity(client *http.Client, symbol string, tipe models.SecurityType) | ||||
| 	return nil, fmt.Errorf("Unable to find security: \"%s\"", symbol) | ||||
| } | ||||
|  | ||||
| func findAccount(client *http.Client, name string, tipe handlers.AccountType, securityid int64) (*handlers.Account, error) { | ||||
| func findAccount(client *http.Client, name string, tipe models.AccountType, securityid int64) (*models.Account, error) { | ||||
| 	accounts, err := getAccounts(client) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -105,11 +104,11 @@ func TestImportOFX401kMutualFunds(t *testing.T) { | ||||
| 			t.Fatalf("Error removing default security: %s\n", err) | ||||
| 		} | ||||
|  | ||||
| 		account := &handlers.Account{ | ||||
| 		account := &models.Account{ | ||||
| 			SecurityId:      d.securities[0].SecurityId, | ||||
| 			UserId:          d.users[0].UserId, | ||||
| 			ParentAccountId: -1, | ||||
| 			Type:            handlers.Investment, | ||||
| 			Type:            models.Investment, | ||||
| 			Name:            "401k", | ||||
| 		} | ||||
|  | ||||
| @@ -130,14 +129,14 @@ func TestImportOFX401kMutualFunds(t *testing.T) { | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error finding VANGUARD TARGET 2045 security: %s\n", err) | ||||
| 		} | ||||
| 		tradingaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", handlers.Trading, security.SecurityId) | ||||
| 		tradingaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", models.Trading, security.SecurityId) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error finding VANGUARD TARGET 2045 trading account: %s\n", err) | ||||
| 		} | ||||
| 		accountBalanceHelper(t, d.clients[0], tradingaccount, "-3.35400") | ||||
|  | ||||
| 		// Ensure actual holding account was created and in the correct place | ||||
| 		investmentaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", handlers.Investment, security.SecurityId) | ||||
| 		investmentaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", models.Investment, security.SecurityId) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error finding VANGUARD TARGET 2045 investment account: %s\n", err) | ||||
| 		} | ||||
| @@ -164,11 +163,11 @@ func TestImportOFXBrokerage(t *testing.T) { | ||||
| 		} | ||||
|  | ||||
| 		// Create the brokerage account | ||||
| 		account := &handlers.Account{ | ||||
| 		account := &models.Account{ | ||||
| 			SecurityId:      d.securities[0].SecurityId, | ||||
| 			UserId:          d.users[0].UserId, | ||||
| 			ParentAccountId: -1, | ||||
| 			Type:            handlers.Investment, | ||||
| 			Type:            models.Investment, | ||||
| 			Name:            "Personal Brokerage", | ||||
| 		} | ||||
|  | ||||
| @@ -185,7 +184,7 @@ func TestImportOFXBrokerage(t *testing.T) { | ||||
|  | ||||
| 		// Make sure the USD trading account was created and has  the right | ||||
| 		// value | ||||
| 		usdtrading, err := findAccount(d.clients[0], "USD", handlers.Trading, d.users[0].DefaultCurrency) | ||||
| 		usdtrading, err := findAccount(d.clients[0], "USD", models.Trading, d.users[0].DefaultCurrency) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error finding USD trading account: %s\n", err) | ||||
| 		} | ||||
| @@ -210,14 +209,14 @@ func TestImportOFXBrokerage(t *testing.T) { | ||||
| 				t.Fatalf("Error finding security: %s\n", err) | ||||
| 			} | ||||
|  | ||||
| 			account, err := findAccount(d.clients[0], check.Name, handlers.Investment, security.SecurityId) | ||||
| 			account, err := findAccount(d.clients[0], check.Name, models.Investment, security.SecurityId) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("Error finding trading account: %s\n", err) | ||||
| 			} | ||||
|  | ||||
| 			accountBalanceHelper(t, d.clients[0], account, check.Balance) | ||||
|  | ||||
| 			tradingaccount, err := findAccount(d.clients[0], check.Name, handlers.Trading, security.SecurityId) | ||||
| 			tradingaccount, err := findAccount(d.clients[0], check.Name, models.Trading, security.SecurityId) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("Error finding trading account: %s\n", err) | ||||
| 			} | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package handlers | ||||
|  | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| ) | ||||
|  | ||||
| @@ -59,7 +60,7 @@ func luaPrice__index(L *lua.LState) int { | ||||
| 		} | ||||
| 		L.Push(SecurityToLua(L, c)) | ||||
| 	case "Value", "value": | ||||
| 		amt, err := GetBigAmount(p.Value) | ||||
| 		amt, err := models.GetBigAmount(p.Value) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|   | ||||
| @@ -39,8 +39,8 @@ type TestData struct { | ||||
| 	clients      []*http.Client | ||||
| 	securities   []models.Security | ||||
| 	prices       []handlers.Price | ||||
| 	accounts     []handlers.Account // accounts must appear after their parents in this slice | ||||
| 	transactions []handlers.Transaction | ||||
| 	accounts     []models.Account // accounts must appear after their parents in this slice | ||||
| 	transactions []models.Transaction | ||||
| 	reports      []handlers.Report | ||||
| 	tabulations  []handlers.Tabulation | ||||
| } | ||||
| @@ -113,7 +113,7 @@ func (t *TestData) Initialize() (*TestData, error) { | ||||
| 	} | ||||
|  | ||||
| 	for i, transaction := range t.transactions { | ||||
| 		transaction.Splits = []*handlers.Split{} | ||||
| 		transaction.Splits = []*models.Split{} | ||||
| 		for _, s := range t.transactions[i].Splits { | ||||
| 			// Make a copy of the split since Splits is a slice of pointers so | ||||
| 			// copying the transaction doesn't | ||||
| @@ -246,78 +246,78 @@ var data = []TestData{ | ||||
| 				RemoteId:   "USDEUR819298714", | ||||
| 			}, | ||||
| 		}, | ||||
| 		accounts: []handlers.Account{ | ||||
| 		accounts: []models.Account{ | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: -1, | ||||
| 				Type:            handlers.Asset, | ||||
| 				Type:            models.Asset, | ||||
| 				Name:            "Assets", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: 0, | ||||
| 				Type:            handlers.Bank, | ||||
| 				Type:            models.Bank, | ||||
| 				Name:            "Credit Union Checking", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: -1, | ||||
| 				Type:            handlers.Expense, | ||||
| 				Type:            models.Expense, | ||||
| 				Name:            "Expenses", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: 2, | ||||
| 				Type:            handlers.Expense, | ||||
| 				Type:            models.Expense, | ||||
| 				Name:            "Groceries", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: 2, | ||||
| 				Type:            handlers.Expense, | ||||
| 				Type:            models.Expense, | ||||
| 				Name:            "Cable", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          1, | ||||
| 				SecurityId:      2, | ||||
| 				ParentAccountId: -1, | ||||
| 				Type:            handlers.Asset, | ||||
| 				Type:            models.Asset, | ||||
| 				Name:            "Assets", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          1, | ||||
| 				SecurityId:      2, | ||||
| 				ParentAccountId: -1, | ||||
| 				Type:            handlers.Expense, | ||||
| 				Type:            models.Expense, | ||||
| 				Name:            "Expenses", | ||||
| 			}, | ||||
| 			{ | ||||
| 				UserId:          0, | ||||
| 				SecurityId:      0, | ||||
| 				ParentAccountId: -1, | ||||
| 				Type:            handlers.Liability, | ||||
| 				Type:            models.Liability, | ||||
| 				Name:            "Credit Card", | ||||
| 			}, | ||||
| 		}, | ||||
| 		transactions: []handlers.Transaction{ | ||||
| 		transactions: []models.Transaction{ | ||||
| 			{ | ||||
| 				UserId:      0, | ||||
| 				Description: "weekly groceries", | ||||
| 				Date:        time.Date(2017, time.October, 15, 1, 16, 59, 0, time.UTC), | ||||
| 				Splits: []*handlers.Split{ | ||||
| 				Splits: []*models.Split{ | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  1, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "-5.6", | ||||
| 					}, | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  3, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "5.6", | ||||
| @@ -328,15 +328,15 @@ var data = []TestData{ | ||||
| 				UserId:      0, | ||||
| 				Description: "weekly groceries", | ||||
| 				Date:        time.Date(2017, time.October, 31, 19, 10, 14, 0, time.UTC), | ||||
| 				Splits: []*handlers.Split{ | ||||
| 				Splits: []*models.Split{ | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  1, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "-81.59", | ||||
| 					}, | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  3, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "81.59", | ||||
| @@ -347,15 +347,15 @@ var data = []TestData{ | ||||
| 				UserId:      0, | ||||
| 				Description: "Cable", | ||||
| 				Date:        time.Date(2017, time.September, 2, 0, 00, 00, 0, time.UTC), | ||||
| 				Splits: []*handlers.Split{ | ||||
| 				Splits: []*models.Split{ | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  1, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "-39.99", | ||||
| 					}, | ||||
| 					{ | ||||
| 						Status:     handlers.Entered, | ||||
| 						Status:     models.Entered, | ||||
| 						AccountId:  4, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "39.99", | ||||
| @@ -366,15 +366,15 @@ var data = []TestData{ | ||||
| 				UserId:      1, | ||||
| 				Description: "Gas", | ||||
| 				Date:        time.Date(2017, time.November, 1, 13, 19, 50, 0, time.UTC), | ||||
| 				Splits: []*handlers.Split{ | ||||
| 				Splits: []*models.Split{ | ||||
| 					{ | ||||
| 						Status:     handlers.Reconciled, | ||||
| 						Status:     models.Reconciled, | ||||
| 						AccountId:  5, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "-24.56", | ||||
| 					}, | ||||
| 					{ | ||||
| 						Status:     handlers.Entered, | ||||
| 						Status:     models.Entered, | ||||
| 						AccountId:  6, | ||||
| 						SecurityId: -1, | ||||
| 						Amount:     "24.56", | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package handlers | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| @@ -10,141 +9,17 @@ import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Split.Status | ||||
| const ( | ||||
| 	Imported   int64 = 1 | ||||
| 	Entered          = 2 | ||||
| 	Cleared          = 3 | ||||
| 	Reconciled       = 4 | ||||
| 	Voided           = 5 | ||||
| ) | ||||
|  | ||||
| // Split.ImportSplitType | ||||
| const ( | ||||
| 	Default         int64 = 0 | ||||
| 	ImportAccount         = 1 // This split belongs to the main account being imported | ||||
| 	SubAccount            = 2 // This split belongs to a sub-account of that being imported | ||||
| 	ExternalAccount       = 3 | ||||
| 	TradingAccount        = 4 | ||||
| 	Commission            = 5 | ||||
| 	Taxes                 = 6 | ||||
| 	Fees                  = 7 | ||||
| 	Load                  = 8 | ||||
| 	IncomeAccount         = 9 | ||||
| 	ExpenseAccount        = 10 | ||||
| ) | ||||
|  | ||||
| type Split struct { | ||||
| 	SplitId         int64 | ||||
| 	TransactionId   int64 | ||||
| 	Status          int64 | ||||
| 	ImportSplitType int64 | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	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() | ||||
| } | ||||
|  | ||||
| func GetBigAmount(amt string) (*big.Rat, error) { | ||||
| 	var r big.Rat | ||||
| 	_, success := r.SetString(amt) | ||||
| 	if !success { | ||||
| 		return nil, errors.New("Couldn't convert string amount to big.Rat via SetString()") | ||||
| 	} | ||||
| 	return &r, nil | ||||
| } | ||||
|  | ||||
| func (s *Split) GetAmount() (*big.Rat, error) { | ||||
| 	return GetBigAmount(s.Amount) | ||||
| } | ||||
|  | ||||
| func (s *Split) Valid() bool { | ||||
| 	if (s.AccountId == -1) == (s.SecurityId == -1) { | ||||
| 		return false | ||||
| 	} | ||||
| 	_, err := s.GetAmount() | ||||
| 	return err == nil | ||||
| } | ||||
|  | ||||
| func (s *Split) AlreadyImported(tx *Tx) (bool, error) { | ||||
| func SplitAlreadyImported(tx *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 | ||||
| } | ||||
|  | ||||
| type Transaction struct { | ||||
| 	TransactionId int64 | ||||
| 	UserId        int64 | ||||
| 	Description   string | ||||
| 	Date          time.Time | ||||
| 	Splits        []*Split `db:"-"` | ||||
| } | ||||
|  | ||||
| type TransactionList struct { | ||||
| 	Transactions *[]Transaction `json:"transactions"` | ||||
| } | ||||
|  | ||||
| type AccountTransactionsList struct { | ||||
| 	Account           *Account | ||||
| 	Transactions      *[]Transaction | ||||
| 	TotalTransactions int64 | ||||
| 	BeginningBalance  string | ||||
| 	EndingBalance     string | ||||
| } | ||||
|  | ||||
| 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) | ||||
| } | ||||
|  | ||||
| func (tl *TransactionList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(tl) | ||||
| } | ||||
|  | ||||
| func (atl *AccountTransactionsList) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(atl) | ||||
| } | ||||
|  | ||||
| func (atl *AccountTransactionsList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(atl) | ||||
| } | ||||
|  | ||||
| func (t *Transaction) Valid() bool { | ||||
| 	for i := range t.Splits { | ||||
| 		if !t.Splits[i].Valid() { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Return a map of security ID's to big.Rat's containing the amount that | ||||
| // security is imbalanced by | ||||
| func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | ||||
| func GetTransactionImbalances(tx *Tx, t *models.Transaction) (map[int64]big.Rat, error) { | ||||
| 	sums := make(map[int64]big.Rat) | ||||
|  | ||||
| 	if !t.Valid() { | ||||
| @@ -155,7 +30,7 @@ func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | ||||
| 		securityid := t.Splits[i].SecurityId | ||||
| 		if t.Splits[i].AccountId != -1 { | ||||
| 			var err error | ||||
| 			var account *Account | ||||
| 			var account *models.Account | ||||
| 			account, err = GetAccount(tx, t.Splits[i].AccountId, t.UserId) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| @@ -172,10 +47,10 @@ func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | ||||
|  | ||||
| // Returns true if all securities contained in this transaction are balanced, | ||||
| // false otherwise | ||||
| func (t *Transaction) Balanced(tx *Tx) (bool, error) { | ||||
| func TransactionBalanced(tx *Tx, t *models.Transaction) (bool, error) { | ||||
| 	var zero big.Rat | ||||
|  | ||||
| 	sums, err := t.GetImbalances(tx) | ||||
| 	sums, err := GetTransactionImbalances(tx, t) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| @@ -188,8 +63,8 @@ func (t *Transaction) Balanced(tx *Tx) (bool, error) { | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
| func GetTransaction(tx *Tx, transactionid int64, userid int64) (*Transaction, error) { | ||||
| 	var t Transaction | ||||
| func GetTransaction(tx *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 { | ||||
| @@ -204,8 +79,8 @@ func GetTransaction(tx *Tx, transactionid int64, userid int64) (*Transaction, er | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func GetTransactions(tx *Tx, userid int64) (*[]Transaction, error) { | ||||
| 	var transactions []Transaction | ||||
| func GetTransactions(tx *Tx, userid int64) (*[]models.Transaction, error) { | ||||
| 	var transactions []models.Transaction | ||||
|  | ||||
| 	_, err := tx.Select(&transactions, "SELECT * from transactions where UserId=?", userid) | ||||
| 	if err != nil { | ||||
| @@ -246,7 +121,7 @@ func (ame AccountMissingError) Error() string { | ||||
| 	return "Account missing" | ||||
| } | ||||
|  | ||||
| func InsertTransaction(tx *Tx, t *Transaction, user *models.User) error { | ||||
| func InsertTransaction(tx *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 { | ||||
| @@ -296,8 +171,8 @@ func InsertTransaction(tx *Tx, t *Transaction, user *models.User) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func UpdateTransaction(tx *Tx, t *Transaction, user *models.User) error { | ||||
| 	var existing_splits []*Split | ||||
| func UpdateTransaction(tx *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 { | ||||
| @@ -373,7 +248,7 @@ func UpdateTransaction(tx *Tx, t *Transaction, user *models.User) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func DeleteTransaction(tx *Tx, t *Transaction, user *models.User) error { | ||||
| func DeleteTransaction(tx *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 { | ||||
| @@ -408,7 +283,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 	} | ||||
|  | ||||
| 	if r.Method == "POST" { | ||||
| 		var transaction Transaction | ||||
| 		var transaction models.Transaction | ||||
| 		if err := ReadJSON(r, &transaction); err != nil { | ||||
| 			return NewError(3 /*Invalid Request*/) | ||||
| 		} | ||||
| @@ -427,7 +302,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		balanced, err := transaction.Balanced(context.Tx) | ||||
| 		balanced, err := TransactionBalanced(context.Tx, &transaction) | ||||
| 		if err != nil { | ||||
| 			return NewError(999 /*Internal Error*/) | ||||
| 		} | ||||
| @@ -449,7 +324,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 	} else if r.Method == "GET" { | ||||
| 		if context.LastLevel() { | ||||
| 			//Return all Transactions | ||||
| 			var al TransactionList | ||||
| 			var al models.TransactionList | ||||
| 			transactions, err := GetTransactions(context.Tx, user.UserId) | ||||
| 			if err != nil { | ||||
| 				log.Print(err) | ||||
| @@ -475,13 +350,13 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 			return NewError(3 /*Invalid Request*/) | ||||
| 		} | ||||
| 		if r.Method == "PUT" { | ||||
| 			var transaction Transaction | ||||
| 			var transaction models.Transaction | ||||
| 			if err := ReadJSON(r, &transaction); err != nil || transaction.TransactionId != transactionid { | ||||
| 				return NewError(3 /*Invalid Request*/) | ||||
| 			} | ||||
| 			transaction.UserId = user.UserId | ||||
|  | ||||
| 			balanced, err := transaction.Balanced(context.Tx) | ||||
| 			balanced, err := TransactionBalanced(context.Tx, &transaction) | ||||
| 			if err != nil { | ||||
| 				log.Print(err) | ||||
| 				return NewError(999 /*Internal Error*/) | ||||
| @@ -526,7 +401,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 	return NewError(3 /*Invalid Request*/) | ||||
| } | ||||
|  | ||||
| func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Transaction) (*big.Rat, error) { | ||||
| func TransactionsBalanceDifference(tx *Tx, 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) | ||||
| @@ -538,7 +413,7 @@ func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Trans | ||||
| 		// 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) | ||||
| 				rat_amount, err := models.GetBigAmount(transactions[i].Splits[j].Amount) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| @@ -551,7 +426,7 @@ func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Trans | ||||
| } | ||||
|  | ||||
| func GetAccountBalance(tx *Tx, user *models.User, accountid int64) (*big.Rat, error) { | ||||
| 	var splits []Split | ||||
| 	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) | ||||
| @@ -561,7 +436,7 @@ func GetAccountBalance(tx *Tx, user *models.User, accountid int64) (*big.Rat, er | ||||
|  | ||||
| 	var balance, tmp big.Rat | ||||
| 	for _, s := range splits { | ||||
| 		rat_amount, err := GetBigAmount(s.Amount) | ||||
| 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @@ -574,7 +449,7 @@ func GetAccountBalance(tx *Tx, user *models.User, accountid int64) (*big.Rat, er | ||||
|  | ||||
| // Assumes accountid is valid and is owned by the current user | ||||
| func GetAccountBalanceDate(tx *Tx, user *models.User, accountid int64, date *time.Time) (*big.Rat, error) { | ||||
| 	var splits []Split | ||||
| 	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) | ||||
| @@ -584,7 +459,7 @@ func GetAccountBalanceDate(tx *Tx, user *models.User, accountid int64, date *tim | ||||
|  | ||||
| 	var balance, tmp big.Rat | ||||
| 	for _, s := range splits { | ||||
| 		rat_amount, err := GetBigAmount(s.Amount) | ||||
| 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @@ -596,7 +471,7 @@ func GetAccountBalanceDate(tx *Tx, user *models.User, accountid int64, date *tim | ||||
| } | ||||
|  | ||||
| func GetAccountBalanceDateRange(tx *Tx, user *models.User, accountid int64, begin, end *time.Time) (*big.Rat, error) { | ||||
| 	var splits []Split | ||||
| 	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) | ||||
| @@ -606,7 +481,7 @@ func GetAccountBalanceDateRange(tx *Tx, user *models.User, accountid int64, begi | ||||
|  | ||||
| 	var balance, tmp big.Rat | ||||
| 	for _, s := range splits { | ||||
| 		rat_amount, err := GetBigAmount(s.Amount) | ||||
| 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @@ -617,9 +492,9 @@ func GetAccountBalanceDateRange(tx *Tx, user *models.User, accountid int64, begi | ||||
| 	return &balance, nil | ||||
| } | ||||
|  | ||||
| func GetAccountTransactions(tx *Tx, user *models.User, accountid int64, sort string, page uint64, limit uint64) (*AccountTransactionsList, error) { | ||||
| 	var transactions []Transaction | ||||
| 	var atl AccountTransactionsList | ||||
| func GetAccountTransactions(tx *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 | ||||
| @@ -685,7 +560,7 @@ func GetAccountTransactions(tx *Tx, user *models.User, accountid int64, sort str | ||||
|  | ||||
| 	var tmp, balance big.Rat | ||||
| 	for _, amount := range amounts { | ||||
| 		rat_amount, err := GetBigAmount(amount) | ||||
| 		rat_amount, err := models.GetBigAmount(amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package handlers_test | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| @@ -10,14 +11,14 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func createTransaction(client *http.Client, transaction *handlers.Transaction) (*handlers.Transaction, error) { | ||||
| 	var s handlers.Transaction | ||||
| func createTransaction(client *http.Client, transaction *models.Transaction) (*models.Transaction, error) { | ||||
| 	var s models.Transaction | ||||
| 	err := create(client, transaction, &s, "/v1/transactions/") | ||||
| 	return &s, err | ||||
| } | ||||
|  | ||||
| func getTransaction(client *http.Client, transactionid int64) (*handlers.Transaction, error) { | ||||
| 	var s handlers.Transaction | ||||
| func getTransaction(client *http.Client, transactionid int64) (*models.Transaction, error) { | ||||
| 	var s models.Transaction | ||||
| 	err := read(client, &s, "/v1/transactions/"+strconv.FormatInt(transactionid, 10)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -25,8 +26,8 @@ func getTransaction(client *http.Client, transactionid int64) (*handlers.Transac | ||||
| 	return &s, nil | ||||
| } | ||||
|  | ||||
| func getTransactions(client *http.Client) (*handlers.TransactionList, error) { | ||||
| 	var tl handlers.TransactionList | ||||
| func getTransactions(client *http.Client) (*models.TransactionList, error) { | ||||
| 	var tl models.TransactionList | ||||
| 	err := read(client, &tl, "/v1/transactions/") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -34,8 +35,8 @@ func getTransactions(client *http.Client) (*handlers.TransactionList, error) { | ||||
| 	return &tl, nil | ||||
| } | ||||
|  | ||||
| func getAccountTransactions(client *http.Client, accountid, page, limit int64, sort string) (*handlers.AccountTransactionsList, error) { | ||||
| 	var atl handlers.AccountTransactionsList | ||||
| func getAccountTransactions(client *http.Client, accountid, page, limit int64, sort string) (*models.AccountTransactionsList, error) { | ||||
| 	var atl models.AccountTransactionsList | ||||
| 	params := url.Values{} | ||||
|  | ||||
| 	query := fmt.Sprintf("/v1/accounts/%d/transactions/", accountid) | ||||
| @@ -57,8 +58,8 @@ func getAccountTransactions(client *http.Client, accountid, page, limit int64, s | ||||
| 	return &atl, nil | ||||
| } | ||||
|  | ||||
| func updateTransaction(client *http.Client, transaction *handlers.Transaction) (*handlers.Transaction, error) { | ||||
| 	var s handlers.Transaction | ||||
| func updateTransaction(client *http.Client, transaction *models.Transaction) (*models.Transaction, error) { | ||||
| 	var s models.Transaction | ||||
| 	err := update(client, transaction, &s, "/v1/transactions/"+strconv.FormatInt(transaction.TransactionId, 10)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -66,7 +67,7 @@ func updateTransaction(client *http.Client, transaction *handlers.Transaction) ( | ||||
| 	return &s, nil | ||||
| } | ||||
|  | ||||
| func deleteTransaction(client *http.Client, s *handlers.Transaction) error { | ||||
| func deleteTransaction(client *http.Client, s *models.Transaction) error { | ||||
| 	err := remove(client, "/v1/transactions/"+strconv.FormatInt(s.TransactionId, 10)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -74,7 +75,7 @@ func deleteTransaction(client *http.Client, s *handlers.Transaction) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ensureTransactionsMatch(t *testing.T, expected, tran *handlers.Transaction, accounts *[]handlers.Account, matchtransactionids, matchsplitids bool) { | ||||
| func ensureTransactionsMatch(t *testing.T, expected, tran *models.Transaction, accounts *[]models.Account, matchtransactionids, matchsplitids bool) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	if tran.TransactionId == 0 { | ||||
| @@ -136,9 +137,9 @@ func ensureTransactionsMatch(t *testing.T, expected, tran *handlers.Transaction, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getAccountVersionMap(t *testing.T, client *http.Client, tran *handlers.Transaction) map[int64]*handlers.Account { | ||||
| func getAccountVersionMap(t *testing.T, client *http.Client, tran *models.Transaction) map[int64]*models.Account { | ||||
| 	t.Helper() | ||||
| 	accountMap := make(map[int64]*handlers.Account) | ||||
| 	accountMap := make(map[int64]*models.Account) | ||||
| 	for _, split := range tran.Splits { | ||||
| 		account, err := getAccount(client, split.AccountId) | ||||
| 		if err != nil { | ||||
| @@ -149,7 +150,7 @@ func getAccountVersionMap(t *testing.T, client *http.Client, tran *handlers.Tran | ||||
| 	return accountMap | ||||
| } | ||||
|  | ||||
| func checkAccountVersionsUpdated(t *testing.T, client *http.Client, accountMap map[int64]*handlers.Account, tran *handlers.Transaction) { | ||||
| func checkAccountVersionsUpdated(t *testing.T, client *http.Client, accountMap map[int64]*models.Account, tran *models.Transaction) { | ||||
| 	for _, split := range tran.Splits { | ||||
| 		account, err := getAccount(client, split.AccountId) | ||||
| 		if err != nil { | ||||
| @@ -177,19 +178,19 @@ func TestCreateTransaction(t *testing.T) { | ||||
| 		} | ||||
|  | ||||
| 		// Don't allow imbalanced transactions | ||||
| 		tran := handlers.Transaction{ | ||||
| 		tran := models.Transaction{ | ||||
| 			UserId:      d.users[0].UserId, | ||||
| 			Description: "Imbalanced", | ||||
| 			Date:        time.Date(2017, time.September, 1, 0, 00, 00, 0, time.UTC), | ||||
| 			Splits: []*handlers.Split{ | ||||
| 			Splits: []*models.Split{ | ||||
| 				{ | ||||
| 					Status:     handlers.Reconciled, | ||||
| 					Status:     models.Reconciled, | ||||
| 					AccountId:  d.accounts[1].AccountId, | ||||
| 					SecurityId: -1, | ||||
| 					Amount:     "-39.98", | ||||
| 				}, | ||||
| 				{ | ||||
| 					Status:     handlers.Entered, | ||||
| 					Status:     models.Entered, | ||||
| 					AccountId:  d.accounts[4].AccountId, | ||||
| 					SecurityId: -1, | ||||
| 					Amount:     "39.99", | ||||
| @@ -209,7 +210,7 @@ func TestCreateTransaction(t *testing.T) { | ||||
| 		} | ||||
|  | ||||
| 		// Don't allow transactions with 0 splits | ||||
| 		tran.Splits = []*handlers.Split{} | ||||
| 		tran.Splits = []*models.Split{} | ||||
| 		_, err = createTransaction(d.clients[0], &tran) | ||||
| 		if err == nil { | ||||
| 			t.Fatalf("Expected error creating with zero splits") | ||||
| @@ -316,9 +317,9 @@ func TestUpdateTransaction(t *testing.T) { | ||||
|  | ||||
| 			ensureTransactionsMatch(t, &curr, tran, nil, true, true) | ||||
|  | ||||
| 			tran.Splits = []*handlers.Split{} | ||||
| 			tran.Splits = []*models.Split{} | ||||
| 			for _, s := range curr.Splits { | ||||
| 				var split handlers.Split | ||||
| 				var split models.Split | ||||
| 				split = *s | ||||
| 				tran.Splits = append(tran.Splits, &split) | ||||
| 			} | ||||
| @@ -346,7 +347,7 @@ func TestUpdateTransaction(t *testing.T) { | ||||
| 			} | ||||
|  | ||||
| 			// Don't allow transactions with 0 splits | ||||
| 			tran.Splits = []*handlers.Split{} | ||||
| 			tran.Splits = []*models.Split{} | ||||
| 			_, err = updateTransaction(d.clients[orig.UserId], tran) | ||||
| 			if err == nil { | ||||
| 				t.Fatalf("Expected error updating with zero splits") | ||||
| @@ -391,12 +392,12 @@ func TestDeleteTransaction(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func helperTestAccountTransactions(t *testing.T, d *TestData, account *handlers.Account, limit int64, sort string) { | ||||
| func helperTestAccountTransactions(t *testing.T, d *TestData, account *models.Account, limit int64, sort string) { | ||||
| 	if account.UserId != d.users[0].UserId { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var transactions []handlers.Transaction | ||||
| 	var transactions []models.Transaction | ||||
| 	var lastFetchCount int64 | ||||
|  | ||||
| 	for page := int64(0); page == 0 || lastFetchCount > 0; page++ { | ||||
|   | ||||
							
								
								
									
										118
									
								
								internal/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								internal/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type AccountType int64 | ||||
|  | ||||
| const ( | ||||
| 	Bank       AccountType = 1 // start at 1 so that the default (0) is invalid | ||||
| 	Cash                   = 2 | ||||
| 	Asset                  = 3 | ||||
| 	Liability              = 4 | ||||
| 	Investment             = 5 | ||||
| 	Income                 = 6 | ||||
| 	Expense                = 7 | ||||
| 	Trading                = 8 | ||||
| 	Equity                 = 9 | ||||
| 	Receivable             = 10 | ||||
| 	Payable                = 11 | ||||
| ) | ||||
|  | ||||
| var AccountTypes = []AccountType{ | ||||
| 	Bank, | ||||
| 	Cash, | ||||
| 	Asset, | ||||
| 	Liability, | ||||
| 	Investment, | ||||
| 	Income, | ||||
| 	Expense, | ||||
| 	Trading, | ||||
| 	Equity, | ||||
| 	Receivable, | ||||
| 	Payable, | ||||
| } | ||||
|  | ||||
| func (t AccountType) String() string { | ||||
| 	switch t { | ||||
| 	case Bank: | ||||
| 		return "Bank" | ||||
| 	case Cash: | ||||
| 		return "Cash" | ||||
| 	case Asset: | ||||
| 		return "Asset" | ||||
| 	case Liability: | ||||
| 		return "Liability" | ||||
| 	case Investment: | ||||
| 		return "Investment" | ||||
| 	case Income: | ||||
| 		return "Income" | ||||
| 	case Expense: | ||||
| 		return "Expense" | ||||
| 	case Trading: | ||||
| 		return "Trading" | ||||
| 	case Equity: | ||||
| 		return "Equity" | ||||
| 	case Receivable: | ||||
| 		return "Receivable" | ||||
| 	case Payable: | ||||
| 		return "Payable" | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type Account struct { | ||||
| 	AccountId         int64 | ||||
| 	ExternalAccountId string | ||||
| 	UserId            int64 | ||||
| 	SecurityId        int64 | ||||
| 	ParentAccountId   int64 // -1 if this account is at the root | ||||
| 	Type              AccountType | ||||
| 	Name              string | ||||
|  | ||||
| 	// monotonically-increasing account transaction version number. Used for | ||||
| 	// allowing a client to ensure they have a consistent version when paging | ||||
| 	// through transactions. | ||||
| 	AccountVersion int64 `json:"Version"` | ||||
|  | ||||
| 	// Optional fields specifying how to fetch transactions from a bank via OFX | ||||
| 	OFXURL       string | ||||
| 	OFXORG       string | ||||
| 	OFXFID       string | ||||
| 	OFXUser      string | ||||
| 	OFXBankID    string // OFX BankID (BrokerID if AcctType == Investment) | ||||
| 	OFXAcctID    string | ||||
| 	OFXAcctType  string // ofxgo.acctType | ||||
| 	OFXClientUID string | ||||
| 	OFXAppID     string | ||||
| 	OFXAppVer    string | ||||
| 	OFXVersion   string | ||||
| 	OFXNoIndent  bool | ||||
| } | ||||
|  | ||||
| type AccountList struct { | ||||
| 	Accounts *[]Account `json:"accounts"` | ||||
| } | ||||
|  | ||||
| func (a *Account) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(a) | ||||
| } | ||||
|  | ||||
| func (a *Account) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(a) | ||||
| } | ||||
|  | ||||
| func (al *AccountList) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(al) | ||||
| } | ||||
|  | ||||
| func (al *AccountList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(al) | ||||
| } | ||||
							
								
								
									
										133
									
								
								internal/models/transactions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								internal/models/transactions.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"math/big" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Split.Status | ||||
| const ( | ||||
| 	Imported   int64 = 1 | ||||
| 	Entered          = 2 | ||||
| 	Cleared          = 3 | ||||
| 	Reconciled       = 4 | ||||
| 	Voided           = 5 | ||||
| ) | ||||
|  | ||||
| // Split.ImportSplitType | ||||
| const ( | ||||
| 	Default         int64 = 0 | ||||
| 	ImportAccount         = 1 // This split belongs to the main account being imported | ||||
| 	SubAccount            = 2 // This split belongs to a sub-account of that being imported | ||||
| 	ExternalAccount       = 3 | ||||
| 	TradingAccount        = 4 | ||||
| 	Commission            = 5 | ||||
| 	Taxes                 = 6 | ||||
| 	Fees                  = 7 | ||||
| 	Load                  = 8 | ||||
| 	IncomeAccount         = 9 | ||||
| 	ExpenseAccount        = 10 | ||||
| ) | ||||
|  | ||||
| type Split struct { | ||||
| 	SplitId         int64 | ||||
| 	TransactionId   int64 | ||||
| 	Status          int64 | ||||
| 	ImportSplitType int64 | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	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() | ||||
| } | ||||
|  | ||||
| func GetBigAmount(amt string) (*big.Rat, error) { | ||||
| 	var r big.Rat | ||||
| 	_, success := r.SetString(amt) | ||||
| 	if !success { | ||||
| 		return nil, errors.New("Couldn't convert string amount to big.Rat via SetString()") | ||||
| 	} | ||||
| 	return &r, nil | ||||
| } | ||||
|  | ||||
| func (s *Split) GetAmount() (*big.Rat, error) { | ||||
| 	return GetBigAmount(s.Amount) | ||||
| } | ||||
|  | ||||
| func (s *Split) Valid() bool { | ||||
| 	if (s.AccountId == -1) == (s.SecurityId == -1) { | ||||
| 		return false | ||||
| 	} | ||||
| 	_, err := s.GetAmount() | ||||
| 	return err == nil | ||||
| } | ||||
|  | ||||
| type Transaction struct { | ||||
| 	TransactionId int64 | ||||
| 	UserId        int64 | ||||
| 	Description   string | ||||
| 	Date          time.Time | ||||
| 	Splits        []*Split `db:"-"` | ||||
| } | ||||
|  | ||||
| type TransactionList struct { | ||||
| 	Transactions *[]Transaction `json:"transactions"` | ||||
| } | ||||
|  | ||||
| type AccountTransactionsList struct { | ||||
| 	Account           *Account | ||||
| 	Transactions      *[]Transaction | ||||
| 	TotalTransactions int64 | ||||
| 	BeginningBalance  string | ||||
| 	EndingBalance     string | ||||
| } | ||||
|  | ||||
| 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) | ||||
| } | ||||
|  | ||||
| func (tl *TransactionList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(tl) | ||||
| } | ||||
|  | ||||
| func (atl *AccountTransactionsList) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(atl) | ||||
| } | ||||
|  | ||||
| func (atl *AccountTransactionsList) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(atl) | ||||
| } | ||||
|  | ||||
| func (t *Transaction) Valid() bool { | ||||
| 	for i := range t.Splits { | ||||
| 		if !t.Splits[i].Valid() { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
		Reference in New Issue
	
	Block a user