mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-30 01:23:26 -04:00 
			
		
		
		
	OFX: Import the most common investment transactions
This is very preliminary support, and is likely to still fail for many cases - even those with nominal support. Most transaction splits end up being filed under 'imbalanced' accounts instead of something that makes more sense.
This commit is contained in:
		
							
								
								
									
										669
									
								
								ofx.go
									
									
									
									
									
								
							
							
						
						
									
										669
									
								
								ofx.go
									
									
									
									
									
								
							| @@ -22,6 +22,16 @@ func (i *OFXImport) GetSecurity(ofxsecurityid int64) (*Security, error) { | ||||
| 	return &i.Securities[ofxsecurityid], nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetSecurityAlternateId(alternateid string, securityType int64) (*Security, error) { | ||||
| 	for _, security := range i.Securities { | ||||
| 		if alternateid == security.AlternateId && securityType == security.Type { | ||||
| 			return &security, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil, errors.New("OFXImport.FindSecurity: Unable to find security") | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetAddCurrency(isoname string) (*Security, error) { | ||||
| 	for _, security := range i.Securities { | ||||
| 		if isoname == security.Name && Currency == security.Type { | ||||
| @@ -83,6 +93,9 @@ 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 | ||||
|  | ||||
| 	security := i.Securities[account.SecurityId-1] | ||||
| 	s1.Amount = amt.FloatString(security.Precision) | ||||
| 	s2.Amount = amt.Neg(amt).FloatString(security.Precision) | ||||
| @@ -152,12 +165,655 @@ func (i *OFXImport) importOFXCC(stmt *ofxgo.CCStatementResponse) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO balance(s) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) importSecurities(seclist *ofxgo.SecurityList) error { | ||||
| 	for _, security := range seclist.Securities { | ||||
| 		var si ofxgo.SecInfo | ||||
| 		if sec, ok := (security).(ofxgo.DebtInfo); ok { | ||||
| 			si = sec.SecInfo | ||||
| 		} else if sec, ok := (security).(ofxgo.MFInfo); ok { | ||||
| 			si = sec.SecInfo | ||||
| 		} else if sec, ok := (security).(ofxgo.OptInfo); ok { | ||||
| 			si = sec.SecInfo | ||||
| 		} else if sec, ok := (security).(ofxgo.OtherInfo); ok { | ||||
| 			si = sec.SecInfo | ||||
| 		} else if sec, ok := (security).(ofxgo.StockInfo); ok { | ||||
| 			si = sec.SecInfo | ||||
| 		} else { | ||||
| 			return errors.New("Can't import unrecognized type satisfying ofxgo.Security interface") | ||||
| 		} | ||||
| 		s := Security{ | ||||
| 			SecurityId:  int64(len(i.Securities) + 1), | ||||
| 			Name:        string(si.SecName), | ||||
| 			Description: string(si.Memo), | ||||
| 			Symbol:      string(si.Ticker), | ||||
| 			Precision:   5, // TODO How to actually determine this? | ||||
| 			Type:        Stock, | ||||
| 			AlternateId: string(si.SecID.UniqueID), | ||||
| 		} | ||||
| 		if len(s.Description) == 0 { | ||||
| 			s.Description = s.Name | ||||
| 		} | ||||
| 		if len(s.Symbol) == 0 { | ||||
| 			s.Symbol = s.Name | ||||
| 		} | ||||
|  | ||||
| 		i.Securities = append(i.Securities, s) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) Transaction { | ||||
| 	var t Transaction | ||||
| 	t.Description = string(invtran.Memo) | ||||
| 	t.Date = invtran.DtTrade.UTC() | ||||
| 	return t | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account *Account) (*Transaction, error) { | ||||
| 	t := i.GetInvTran(&buy.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(buy.SecID.UniqueID), Stock) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	memo := string(buy.InvTran.Memo) | ||||
| 	if len(memo) > 0 { | ||||
| 		memo += " " | ||||
| 	} | ||||
|  | ||||
| 	var commission, taxes, fees, load, total, tradingTotal big.Rat | ||||
| 	commission.Set(&buy.Commission.Rat) | ||||
| 	taxes.Set(&buy.Taxes.Rat) | ||||
| 	fees.Set(&buy.Fees.Rat) | ||||
| 	load.Set(&buy.Load.Rat) | ||||
| 	total.Set(&buy.Total.Rat) | ||||
|  | ||||
| 	tradingTotal.Neg(&total) | ||||
| 	tradingTotal.Sub(&tradingTotal, &commission) | ||||
| 	tradingTotal.Sub(&tradingTotal, &taxes) | ||||
| 	tradingTotal.Sub(&tradingTotal, &fees) | ||||
| 	tradingTotal.Sub(&tradingTotal, &load) | ||||
|  | ||||
| 	// Convert amounts to account's currency if Currency is set | ||||
| 	if ok, _ := buy.Currency.Valid(); ok { | ||||
| 		commission.Mul(&commission, &buy.Currency.CurRate.Rat) | ||||
| 		taxes.Mul(&taxes, &buy.Currency.CurRate.Rat) | ||||
| 		fees.Mul(&fees, &buy.Currency.CurRate.Rat) | ||||
| 		load.Mul(&load, &buy.Currency.CurRate.Rat) | ||||
| 		total.Mul(&total, &buy.Currency.CurRate.Rat) | ||||
| 		tradingTotal.Mul(&tradingTotal, &buy.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(commission)", | ||||
| 			Amount:          commission.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(taxes)", | ||||
| 			Amount:          taxes.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(fees)", | ||||
| 			Amount:          fees.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(load)", | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: 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{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          tradingTotal.FloatString(curdef.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	units := big.NewRat(0, 1) | ||||
| 	units.Set(&buy.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *Security, account *Account) (*Transaction, error) { | ||||
| 	t := i.GetInvTran(&income.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(income.SecID.UniqueID), Stock) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	memo := string(income.InvTran.Memo) | ||||
| 	if len(memo) > 0 { | ||||
| 		memo += " " | ||||
| 	} else { | ||||
| 		memo = income.IncomeType.String() + " on " + security.Symbol | ||||
| 	} | ||||
|  | ||||
| 	var total big.Rat | ||||
| 	total.Set(&income.Total.Rat) | ||||
| 	if ok, _ := income.Currency.Valid(); ok { | ||||
| 		total.Mul(&total, &income.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ImportAccount, | ||||
| 		AccountId:       account.AccountId, | ||||
| 		SecurityId:      -1, | ||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, account *Account) (*Transaction, error) { | ||||
| 	t := i.GetInvTran(&reinvest.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(reinvest.SecID.UniqueID), Stock) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	memo := string(reinvest.InvTran.Memo) | ||||
| 	if len(memo) > 0 { | ||||
| 		memo += " " | ||||
| 	} | ||||
|  | ||||
| 	var commission, taxes, fees, load, total, tradingTotal big.Rat | ||||
| 	commission.Set(&reinvest.Commission.Rat) | ||||
| 	taxes.Set(&reinvest.Taxes.Rat) | ||||
| 	fees.Set(&reinvest.Fees.Rat) | ||||
| 	load.Set(&reinvest.Load.Rat) | ||||
| 	total.Set(&reinvest.Total.Rat) | ||||
|  | ||||
| 	tradingTotal.Neg(&total) | ||||
| 	tradingTotal.Sub(&tradingTotal, &commission) | ||||
| 	tradingTotal.Sub(&tradingTotal, &taxes) | ||||
| 	tradingTotal.Sub(&tradingTotal, &fees) | ||||
| 	tradingTotal.Sub(&tradingTotal, &load) | ||||
|  | ||||
| 	// Convert amounts to account's currency if Currency is set | ||||
| 	if ok, _ := reinvest.Currency.Valid(); ok { | ||||
| 		commission.Mul(&commission, &reinvest.Currency.CurRate.Rat) | ||||
| 		taxes.Mul(&taxes, &reinvest.Currency.CurRate.Rat) | ||||
| 		fees.Mul(&fees, &reinvest.Currency.CurRate.Rat) | ||||
| 		load.Mul(&load, &reinvest.Currency.CurRate.Rat) | ||||
| 		total.Mul(&total, &reinvest.Currency.CurRate.Rat) | ||||
| 		tradingTotal.Mul(&tradingTotal, &reinvest.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(commission)", | ||||
| 			Amount:          commission.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(taxes)", | ||||
| 			Amount:          taxes.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(fees)", | ||||
| 			Amount:          fees.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(load)", | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: 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{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: IncomeAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          total.FloatString(curdef.Precision), | ||||
| 	}) | ||||
| 	total.Neg(&total) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: 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{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          tradingTotal.FloatString(curdef.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	units.Set(&reinvest.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, account *Account) (*Transaction, error) { | ||||
| 	t := i.GetInvTran(&sell.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(sell.SecID.UniqueID), Stock) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	memo := string(sell.InvTran.Memo) | ||||
| 	if len(memo) > 0 { | ||||
| 		memo += " " | ||||
| 	} | ||||
|  | ||||
| 	var commission, taxes, fees, load, total, tradingTotal big.Rat | ||||
| 	commission.Set(&sell.Commission.Rat) | ||||
| 	taxes.Set(&sell.Taxes.Rat) | ||||
| 	fees.Set(&sell.Fees.Rat) | ||||
| 	load.Set(&sell.Load.Rat) | ||||
| 	total.Set(&sell.Total.Rat) | ||||
|  | ||||
| 	tradingTotal.Neg(&total) | ||||
| 	tradingTotal.Sub(&tradingTotal, &commission) | ||||
| 	tradingTotal.Sub(&tradingTotal, &taxes) | ||||
| 	tradingTotal.Sub(&tradingTotal, &fees) | ||||
| 	tradingTotal.Sub(&tradingTotal, &load) | ||||
|  | ||||
| 	// Convert amounts to account's currency if Currency is set | ||||
| 	if ok, _ := sell.Currency.Valid(); ok { | ||||
| 		commission.Mul(&commission, &sell.Currency.CurRate.Rat) | ||||
| 		taxes.Mul(&taxes, &sell.Currency.CurRate.Rat) | ||||
| 		fees.Mul(&fees, &sell.Currency.CurRate.Rat) | ||||
| 		load.Mul(&load, &sell.Currency.CurRate.Rat) | ||||
| 		total.Mul(&total, &sell.Currency.CurRate.Rat) | ||||
| 		tradingTotal.Mul(&tradingTotal, &sell.Currency.CurRate.Rat) | ||||
| 	} | ||||
|  | ||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Commission, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(commission)", | ||||
| 			Amount:          commission.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Taxes, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(taxes)", | ||||
| 			Amount:          taxes.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Fees, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(fees)", | ||||
| 			Amount:          fees.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||
| 		t.Splits = append(t.Splits, &Split{ | ||||
| 			// TODO ReversalFiTID? | ||||
| 			Status:          Imported, | ||||
| 			ImportSplitType: Load, | ||||
| 			AccountId:       -1, | ||||
| 			SecurityId:      curdef.SecurityId, | ||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 			Memo:            memo + "(load)", | ||||
| 			Amount:          load.FloatString(curdef.Precision), | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: 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{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      curdef.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          tradingTotal.FloatString(curdef.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	units.Set(&sell.Units.Rat) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: TradingAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) (*Transaction, error) { | ||||
| 	t := i.GetInvTran(&transfer.InvTran) | ||||
|  | ||||
| 	security, err := i.GetSecurityAlternateId(string(transfer.SecID.UniqueID), Stock) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	memo := string(transfer.InvTran.Memo) | ||||
|  | ||||
| 	var units big.Rat | ||||
| 	if transfer.TferAction == ofxgo.TferActionIn { | ||||
| 		units.Set(&transfer.Units.Rat) | ||||
| 	} else { | ||||
| 		units.Neg(&transfer.Units.Rat) | ||||
| 	} | ||||
|  | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: SubAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
| 	units.Neg(&units) | ||||
| 	t.Splits = append(t.Splits, &Split{ | ||||
| 		// TODO ReversalFiTID? | ||||
| 		Status:          Imported, | ||||
| 		ImportSplitType: ExternalAccount, | ||||
| 		AccountId:       -1, | ||||
| 		SecurityId:      security.SecurityId, | ||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||
| 		Memo:            memo, | ||||
| 		Amount:          units.FloatString(security.Precision), | ||||
| 	}) | ||||
|  | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) AddInvTransaction(invtran *ofxgo.InvTransaction, account *Account, curdef *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 err error | ||||
| 	if tran, ok := (*invtran).(ofxgo.BuyDebt); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.BuyMF); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.BuyOpt); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.BuyOther); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.BuyStock); ok { | ||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||
| 		// TODO implementme | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.ClosureOpt); ok { | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.Income); ok { | ||||
| 		t, err = i.GetIncomeTran(&tran, curdef, account) | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.InvExpense); ok { | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.JrnlFund); ok { | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.JrnlSec); ok { | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.MarginInterest); ok { | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.Reinvest); ok { | ||||
| 		t, err = i.GetReinvestTran(&tran, curdef, account) | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.RetOfCap); ok { | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.SellDebt); ok { | ||||
| 		t, err = i.GetInvSellTran(&tran.InvSell, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.SellMF); ok { | ||||
| 		t, err = i.GetInvSellTran(&tran.InvSell, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.SellOpt); ok { | ||||
| 		t, err = i.GetInvSellTran(&tran.InvSell, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.SellOther); ok { | ||||
| 		t, err = i.GetInvSellTran(&tran.InvSell, curdef, account) | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.SellStock); ok { | ||||
| 		t, err = i.GetInvSellTran(&tran.InvSell, curdef, account) | ||||
| 		// TODO implementme | ||||
| 		//	} else if tran, ok := (*invtran).(ofxgo.Split); ok { | ||||
| 	} else if tran, ok := (*invtran).(ofxgo.Transfer); ok { | ||||
| 		t, err = i.GetTransferTran(&tran, account) | ||||
| 	} else { | ||||
| 		return errors.New("Unrecognized type satisfying ofxgo.InvTransaction interface: " + (*invtran).TransactionType()) | ||||
| 		return nil | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	i.Transactions = append(i.Transactions, *t) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (i *OFXImport) importOFXInv(stmt *ofxgo.InvStatementResponse) error { | ||||
| 	// TODO | ||||
| 	return errors.New("unimplemented") | ||||
| 	security, err := i.GetAddCurrency(stmt.CurDef.String()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	account := Account{ | ||||
| 		AccountId:         int64(len(i.Accounts) + 1), | ||||
| 		ExternalAccountId: stmt.InvAcctFrom.AcctID.String(), | ||||
| 		SecurityId:        security.SecurityId, | ||||
| 		ParentAccountId:   -1, | ||||
| 		Type:              Investment, | ||||
| 	} | ||||
| 	i.Accounts = append(i.Accounts, account) | ||||
|  | ||||
| 	if stmt.InvTranList != nil { | ||||
| 		for _, invtran := range stmt.InvTranList.InvTransactions { | ||||
| 			if err := i.AddInvTransaction(&invtran, &account, security); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		for _, bt := range stmt.InvTranList.BankTransactions { | ||||
| 			// TODO Should we do something different for the value of | ||||
| 			// bt.SubAcctFund? | ||||
| 			for _, tran := range bt.Transactions { | ||||
| 				if err := i.AddTransaction(&tran, &account); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO InvPosList | ||||
| 	// TODO InvBal | ||||
| 	// TODO Inv401K and INV401kBal??? | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ImportOFX(r io.Reader) (*OFXImport, error) { | ||||
| @@ -191,6 +847,15 @@ func ImportOFX(r io.Reader) (*OFXImport, error) { | ||||
| 			return &i, nil | ||||
| 		} | ||||
| 	} | ||||
| 	for _, seclist := range response.SecList { | ||||
| 		if securitylist, ok := seclist.(*ofxgo.SecurityList); ok { | ||||
| 			err = i.importSecurities(securitylist) | ||||
| 			// TODO actually import securities | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for _, inv := range response.InvStmt { | ||||
| 		if stmt, ok := inv.(*ofxgo.InvStatementResponse); ok { | ||||
| 			err = i.importOFXInv(stmt) | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Split.Status | ||||
| const ( | ||||
| 	Imported   int64 = 1 | ||||
| 	Entered          = 2 | ||||
| @@ -22,10 +23,25 @@ const ( | ||||
| 	Voided           = 5 | ||||
| ) | ||||
|  | ||||
| // Split.ImportSplitType | ||||
| const ( | ||||
| 	Default         int64 = 0 | ||||
| 	ImportAccount         = 1 | ||||
| 	SubAccount            = 2 | ||||
| 	ExternalAccount       = 3 | ||||
| 	TradingAccount        = 4 | ||||
| 	Commission            = 5 | ||||
| 	Taxes                 = 6 | ||||
| 	Fees                  = 7 | ||||
| 	Load                  = 8 | ||||
| 	IncomeAccount         = 9 | ||||
| ) | ||||
|  | ||||
| type Split struct { | ||||
| 	SplitId       int64 | ||||
| 	TransactionId int64 | ||||
| 	Status        int64 | ||||
| 	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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user