-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
import transactions from ofx 2.x file
- Loading branch information
Showing
11 changed files
with
645 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package ofx | ||
|
||
import ( | ||
"encoding/xml" | ||
|
||
"github.com/mayswind/ezbookkeeping/pkg/models" | ||
) | ||
|
||
const ofxVersion1 = "100" | ||
const ofxVersion2 = "200" | ||
|
||
const ofxDefaultTimezoneOffset = "+00:00" | ||
|
||
// ofxAccountType represents account type in open financial exchange (ofx) file | ||
type ofxAccountType string | ||
|
||
// OFX account types | ||
const ( | ||
ofxCheckingAccount ofxAccountType = "CHECKING" | ||
ofxSavingsAccount ofxAccountType = "SAVINGS" | ||
ofxMoneyMarketAccount ofxAccountType = "MONEYMRKT" | ||
ofxLineOfCreditAccount ofxAccountType = "CREDITLINE" | ||
ofxCertificateOfDepositAccount ofxAccountType = "CD" | ||
) | ||
|
||
// ofxTransactionType represents transaction type in open financial exchange (ofx) file | ||
type ofxTransactionType string | ||
|
||
// OFX transaction types | ||
const ( | ||
ofxGenericCreditTransaction ofxTransactionType = "CREDIT" | ||
ofxGenericDebitTransaction ofxTransactionType = "DEBIT" | ||
ofxInterestTransaction ofxTransactionType = "INT" | ||
ofxDividendTransaction ofxTransactionType = "DIV" | ||
ofxFIFeeTransaction ofxTransactionType = "FEE" | ||
ofxServiceChargeTransaction ofxTransactionType = "SRVCHG" | ||
ofxDepositTransaction ofxTransactionType = "DEP" | ||
ofxATMTransaction ofxTransactionType = "ATM" | ||
ofxPOSTransaction ofxTransactionType = "POS" | ||
ofxTransferTransaction ofxTransactionType = "XFER" | ||
ofxCheckTransaction ofxTransactionType = "CHECK" | ||
ofxElectronicPaymentTransaction ofxTransactionType = "PAYMENT" | ||
ofxCashWithdrawalTransaction ofxTransactionType = "CASH" | ||
ofxDirectDepositTransaction ofxTransactionType = "DIRECTDEP" | ||
ofxMerchantInitiatedDebitTransaction ofxTransactionType = "DIRECTDEBIT" | ||
ofxRepeatingPaymentTransaction ofxTransactionType = "REPEATPMT" | ||
ofxHoldTransaction ofxTransactionType = "HOLD" | ||
ofxOtherTransaction ofxTransactionType = "OTHER" | ||
) | ||
|
||
var ofxTransactionTypeMapping = map[ofxTransactionType]models.TransactionType{ | ||
ofxGenericCreditTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxGenericDebitTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxDividendTransaction: models.TRANSACTION_TYPE_INCOME, | ||
ofxFIFeeTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxServiceChargeTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxDepositTransaction: models.TRANSACTION_TYPE_INCOME, | ||
ofxTransferTransaction: models.TRANSACTION_TYPE_TRANSFER, | ||
ofxCheckTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxElectronicPaymentTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxCashWithdrawalTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxDirectDepositTransaction: models.TRANSACTION_TYPE_INCOME, | ||
ofxMerchantInitiatedDebitTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
ofxRepeatingPaymentTransaction: models.TRANSACTION_TYPE_EXPENSE, | ||
} | ||
|
||
// ofxFile represents the struct of open financial exchange (ofx) file | ||
type ofxFile struct { | ||
XMLName xml.Name `xml:"OFX"` | ||
FileHeader *ofxFileHeader | ||
BankMessageResponseV1 *ofxBankMessageResponseV1 `xml:"BANKMSGSRSV1"` | ||
CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1 `xml:"CREDITCARDMSGSRSV1"` | ||
} | ||
|
||
// ofxFileHeader represents the struct of open financial exchange (ofx) file header | ||
type ofxFileHeader struct { | ||
OFXVersion string | ||
OFXDataVersion string | ||
Security string | ||
OldFileUid string | ||
NewFileUid string | ||
} | ||
|
||
// ofxBankMessageResponseV1 represents the struct of open financial exchange (ofx) bank message response v1 | ||
type ofxBankMessageResponseV1 struct { | ||
StatementTransactionResponse *ofxBankStatementTransactionResponse `xml:"STMTTRNRS"` | ||
} | ||
|
||
// ofxCreditCardMessageResponseV1 represents the struct of open financial exchange (ofx) credit card message response v1 | ||
type ofxCreditCardMessageResponseV1 struct { | ||
StatementTransactionResponse *ofxCreditCardStatementTransactionResponse `xml:"CCSTMTTRNRS"` | ||
} | ||
|
||
// ofxBankStatementTransactionResponse represents the struct of open financial exchange (ofx) bank statement transaction response | ||
type ofxBankStatementTransactionResponse struct { | ||
StatementResponse *ofxBankStatementResponse `xml:"STMTRS"` | ||
} | ||
|
||
// ofxCreditCardStatementTransactionResponse represents the struct of open financial exchange (ofx) credit card statement transaction response | ||
type ofxCreditCardStatementTransactionResponse struct { | ||
StatementResponse *ofxCreditCardStatementResponse `xml:"CCSTMTRS"` | ||
} | ||
|
||
// ofxBankStatementResponse represents the struct of open financial exchange (ofx) bank statement response | ||
type ofxBankStatementResponse struct { | ||
DefaultCurrency string `xml:"CURDEF"` | ||
AccountFrom *ofxBankAccount `xml:"BANKACCTFROM"` | ||
TransactionList *ofxBankTransactionList `xml:"BANKTRANLIST"` | ||
} | ||
|
||
// ofxCreditCardStatementResponse represents the struct of open financial exchange (ofx) credit card statement response | ||
type ofxCreditCardStatementResponse struct { | ||
DefaultCurrency string `xml:"CURDEF"` | ||
AccountFrom *ofxCreditCardAccount `xml:"CCACCTFROM"` | ||
TransactionList *ofxCreditCardTransactionList `xml:"BANKTRANLIST"` | ||
} | ||
|
||
// ofxBankAccount represents the struct of open financial exchange (ofx) bank account | ||
type ofxBankAccount struct { | ||
BankId string `xml:"BANKID"` | ||
BranchId string `xml:"BRANCHID"` | ||
AccountId string `xml:"ACCTID"` | ||
AccountType ofxAccountType `xml:"ACCTTYPE"` | ||
AccountKey string `xml:"ACCTKEY"` | ||
} | ||
|
||
// ofxCreditCardAccount represents the struct of open financial exchange (ofx) credit card account | ||
type ofxCreditCardAccount struct { | ||
AccountId string `xml:"ACCTID"` | ||
AccountKey string `xml:"ACCTKEY"` | ||
} | ||
|
||
// ofxBankTransactionList represents the struct of open financial exchange (ofx) bank transaction list | ||
type ofxBankTransactionList struct { | ||
StartDate string `xml:"DTSTART"` | ||
EndDate string `xml:"DTEND"` | ||
StatementTransactions []*ofxBankStatementTransaction `xml:"STMTTRN"` | ||
} | ||
|
||
// ofxCreditCardTransactionList represents the struct of open financial exchange (ofx) credit card transaction list | ||
type ofxCreditCardTransactionList struct { | ||
StartDate string `xml:"DTSTART"` | ||
EndDate string `xml:"DTEND"` | ||
StatementTransactions []*ofxCreditCardStatementTransaction `xml:"STMTTRN"` | ||
} | ||
|
||
// ofxBasicStatementTransaction represents the struct of open financial exchange (ofx) basic statement transaction | ||
type ofxBasicStatementTransaction struct { | ||
TransactionId string `xml:"FITID"` | ||
TransactionType ofxTransactionType `xml:"TRNTYPE"` | ||
PostedDate string `xml:"DTPOSTED"` | ||
Amount string `xml:"TRNAMT"` | ||
Name string `xml:"NAME"` | ||
Payee *ofxPayee `xml:"PAYEE"` | ||
Memo string `xml:"MEMO"` | ||
Currency string `xml:"CURRENCY"` | ||
OriginalCurrency string `xml:"ORIGCURRENCY"` | ||
} | ||
|
||
// ofxBankStatementTransaction represents the struct of open financial exchange (ofx) bank statement transaction | ||
type ofxBankStatementTransaction struct { | ||
ofxBasicStatementTransaction | ||
AccountTo *ofxCreditCardAccount `xml:"BANKACCTTO"` | ||
} | ||
|
||
// ofxCreditCardStatementTransaction represents the struct of open financial exchange (ofx) credit card statement transaction | ||
type ofxCreditCardStatementTransaction struct { | ||
ofxBasicStatementTransaction | ||
AccountTo *ofxCreditCardAccount `xml:"CCACCTTO"` | ||
} | ||
|
||
// ofxPayee represents the struct of open financial exchange (ofx) payee info | ||
type ofxPayee struct { | ||
Name string `xml:"NAME"` | ||
Address1 string `xml:"ADDR1"` | ||
Address2 string `xml:"ADDR2"` | ||
Address3 string `xml:"ADDR3"` | ||
City string `xml:"CITY"` | ||
State string `xml:"STATE"` | ||
PostalCode string `xml:"POSTALCODE"` | ||
Country string `xml:"COUNTRY"` | ||
Phone string `xml:"PHONE"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package ofx | ||
|
||
import ( | ||
"bytes" | ||
"encoding/xml" | ||
|
||
"github.com/mayswind/ezbookkeeping/pkg/core" | ||
"github.com/mayswind/ezbookkeeping/pkg/errs" | ||
"github.com/mayswind/ezbookkeeping/pkg/utils" | ||
) | ||
|
||
// ofxFileReader defines the structure of open financial exchange (ofx) file reader | ||
type ofxFileReader struct { | ||
xmlDecoder *xml.Decoder | ||
} | ||
|
||
// read returns the imported open financial exchange (ofx) file | ||
func (r *ofxFileReader) read(ctx core.Context) (*ofxFile, error) { | ||
file := &ofxFile{} | ||
|
||
err := r.xmlDecoder.Decode(&file) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return file, nil | ||
} | ||
|
||
func createNewOFXFileReader(data []byte) (*ofxFileReader, error) { | ||
if len(data) > 5 && data[0] == 0x3C && data[1] == 0x3F && data[2] == 0x78 && data[3] == 0x6D && data[4] == 0x6C { // ofx 2.x starts with <?xml | ||
xmlDecoder := xml.NewDecoder(bytes.NewReader(data)) | ||
xmlDecoder.CharsetReader = utils.IdentReader | ||
|
||
return &ofxFileReader{ | ||
xmlDecoder: xmlDecoder, | ||
}, nil | ||
} else if len(data) > 13 && string(data[0:13]) == "OFXHEADER:100" { // ofx 1.x starts with OFXHEADER:100 | ||
|
||
} else if len(data) > 5 && string(data[0:5]) == "<OFX>" { // no ofx header | ||
xmlDecoder := xml.NewDecoder(bytes.NewReader(data)) | ||
xmlDecoder.CharsetReader = utils.IdentReader | ||
|
||
return &ofxFileReader{ | ||
xmlDecoder: xmlDecoder, | ||
}, nil | ||
} | ||
|
||
return nil, errs.ErrInvalidOFXFile | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package ofx | ||
|
||
import ( | ||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" | ||
"github.com/mayswind/ezbookkeeping/pkg/core" | ||
"github.com/mayswind/ezbookkeeping/pkg/models" | ||
"github.com/mayswind/ezbookkeeping/pkg/utils" | ||
) | ||
|
||
var ofxTransactionTypeNameMapping = map[models.TransactionType]string{ | ||
models.TRANSACTION_TYPE_INCOME: utils.IntToString(int(models.TRANSACTION_TYPE_INCOME)), | ||
models.TRANSACTION_TYPE_EXPENSE: utils.IntToString(int(models.TRANSACTION_TYPE_EXPENSE)), | ||
models.TRANSACTION_TYPE_TRANSFER: utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER)), | ||
} | ||
|
||
// ofxTransactionDataImporter defines the structure of open financial exchange (ofx) file importer for transaction data | ||
type ofxTransactionDataImporter struct { | ||
} | ||
|
||
// Initialize a open financial exchange (ofx) transaction data importer singleton instance | ||
var ( | ||
OFXTransactionDataImporter = &ofxTransactionDataImporter{} | ||
) | ||
|
||
// ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data | ||
func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { | ||
ofxDataReader, err := createNewOFXFileReader(data) | ||
|
||
if err != nil { | ||
return nil, nil, nil, nil, nil, nil, err | ||
} | ||
|
||
ofxFile, err := ofxDataReader.read(ctx) | ||
|
||
if err != nil { | ||
return nil, nil, nil, nil, nil, nil, err | ||
} | ||
|
||
transactionDataTable, err := createNewOFXTransactionDataTable(ofxFile) | ||
|
||
if err != nil { | ||
return nil, nil, nil, nil, nil, nil, err | ||
} | ||
|
||
dataTableImporter := datatable.CreateNewSimpleImporter(ofxTransactionTypeNameMapping) | ||
|
||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) | ||
} |
Oops, something went wrong.