WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit c372272

Browse files
committed
import transactions from ofx 2.x file
1 parent 22d653c commit c372272

File tree

11 files changed

+645
-0
lines changed

11 files changed

+645
-0
lines changed

pkg/converters/ofx/ofx_data.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package ofx
2+
3+
import (
4+
"encoding/xml"
5+
6+
"github.com/mayswind/ezbookkeeping/pkg/models"
7+
)
8+
9+
const ofxVersion1 = "100"
10+
const ofxVersion2 = "200"
11+
12+
const ofxDefaultTimezoneOffset = "+00:00"
13+
14+
// ofxAccountType represents account type in open financial exchange (ofx) file
15+
type ofxAccountType string
16+
17+
// OFX account types
18+
const (
19+
ofxCheckingAccount ofxAccountType = "CHECKING"
20+
ofxSavingsAccount ofxAccountType = "SAVINGS"
21+
ofxMoneyMarketAccount ofxAccountType = "MONEYMRKT"
22+
ofxLineOfCreditAccount ofxAccountType = "CREDITLINE"
23+
ofxCertificateOfDepositAccount ofxAccountType = "CD"
24+
)
25+
26+
// ofxTransactionType represents transaction type in open financial exchange (ofx) file
27+
type ofxTransactionType string
28+
29+
// OFX transaction types
30+
const (
31+
ofxGenericCreditTransaction ofxTransactionType = "CREDIT"
32+
ofxGenericDebitTransaction ofxTransactionType = "DEBIT"
33+
ofxInterestTransaction ofxTransactionType = "INT"
34+
ofxDividendTransaction ofxTransactionType = "DIV"
35+
ofxFIFeeTransaction ofxTransactionType = "FEE"
36+
ofxServiceChargeTransaction ofxTransactionType = "SRVCHG"
37+
ofxDepositTransaction ofxTransactionType = "DEP"
38+
ofxATMTransaction ofxTransactionType = "ATM"
39+
ofxPOSTransaction ofxTransactionType = "POS"
40+
ofxTransferTransaction ofxTransactionType = "XFER"
41+
ofxCheckTransaction ofxTransactionType = "CHECK"
42+
ofxElectronicPaymentTransaction ofxTransactionType = "PAYMENT"
43+
ofxCashWithdrawalTransaction ofxTransactionType = "CASH"
44+
ofxDirectDepositTransaction ofxTransactionType = "DIRECTDEP"
45+
ofxMerchantInitiatedDebitTransaction ofxTransactionType = "DIRECTDEBIT"
46+
ofxRepeatingPaymentTransaction ofxTransactionType = "REPEATPMT"
47+
ofxHoldTransaction ofxTransactionType = "HOLD"
48+
ofxOtherTransaction ofxTransactionType = "OTHER"
49+
)
50+
51+
var ofxTransactionTypeMapping = map[ofxTransactionType]models.TransactionType{
52+
ofxGenericCreditTransaction: models.TRANSACTION_TYPE_EXPENSE,
53+
ofxGenericDebitTransaction: models.TRANSACTION_TYPE_EXPENSE,
54+
ofxDividendTransaction: models.TRANSACTION_TYPE_INCOME,
55+
ofxFIFeeTransaction: models.TRANSACTION_TYPE_EXPENSE,
56+
ofxServiceChargeTransaction: models.TRANSACTION_TYPE_EXPENSE,
57+
ofxDepositTransaction: models.TRANSACTION_TYPE_INCOME,
58+
ofxTransferTransaction: models.TRANSACTION_TYPE_TRANSFER,
59+
ofxCheckTransaction: models.TRANSACTION_TYPE_EXPENSE,
60+
ofxElectronicPaymentTransaction: models.TRANSACTION_TYPE_EXPENSE,
61+
ofxCashWithdrawalTransaction: models.TRANSACTION_TYPE_EXPENSE,
62+
ofxDirectDepositTransaction: models.TRANSACTION_TYPE_INCOME,
63+
ofxMerchantInitiatedDebitTransaction: models.TRANSACTION_TYPE_EXPENSE,
64+
ofxRepeatingPaymentTransaction: models.TRANSACTION_TYPE_EXPENSE,
65+
}
66+
67+
// ofxFile represents the struct of open financial exchange (ofx) file
68+
type ofxFile struct {
69+
XMLName xml.Name `xml:"OFX"`
70+
FileHeader *ofxFileHeader
71+
BankMessageResponseV1 *ofxBankMessageResponseV1 `xml:"BANKMSGSRSV1"`
72+
CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1 `xml:"CREDITCARDMSGSRSV1"`
73+
}
74+
75+
// ofxFileHeader represents the struct of open financial exchange (ofx) file header
76+
type ofxFileHeader struct {
77+
OFXVersion string
78+
OFXDataVersion string
79+
Security string
80+
OldFileUid string
81+
NewFileUid string
82+
}
83+
84+
// ofxBankMessageResponseV1 represents the struct of open financial exchange (ofx) bank message response v1
85+
type ofxBankMessageResponseV1 struct {
86+
StatementTransactionResponse *ofxBankStatementTransactionResponse `xml:"STMTTRNRS"`
87+
}
88+
89+
// ofxCreditCardMessageResponseV1 represents the struct of open financial exchange (ofx) credit card message response v1
90+
type ofxCreditCardMessageResponseV1 struct {
91+
StatementTransactionResponse *ofxCreditCardStatementTransactionResponse `xml:"CCSTMTTRNRS"`
92+
}
93+
94+
// ofxBankStatementTransactionResponse represents the struct of open financial exchange (ofx) bank statement transaction response
95+
type ofxBankStatementTransactionResponse struct {
96+
StatementResponse *ofxBankStatementResponse `xml:"STMTRS"`
97+
}
98+
99+
// ofxCreditCardStatementTransactionResponse represents the struct of open financial exchange (ofx) credit card statement transaction response
100+
type ofxCreditCardStatementTransactionResponse struct {
101+
StatementResponse *ofxCreditCardStatementResponse `xml:"CCSTMTRS"`
102+
}
103+
104+
// ofxBankStatementResponse represents the struct of open financial exchange (ofx) bank statement response
105+
type ofxBankStatementResponse struct {
106+
DefaultCurrency string `xml:"CURDEF"`
107+
AccountFrom *ofxBankAccount `xml:"BANKACCTFROM"`
108+
TransactionList *ofxBankTransactionList `xml:"BANKTRANLIST"`
109+
}
110+
111+
// ofxCreditCardStatementResponse represents the struct of open financial exchange (ofx) credit card statement response
112+
type ofxCreditCardStatementResponse struct {
113+
DefaultCurrency string `xml:"CURDEF"`
114+
AccountFrom *ofxCreditCardAccount `xml:"CCACCTFROM"`
115+
TransactionList *ofxCreditCardTransactionList `xml:"BANKTRANLIST"`
116+
}
117+
118+
// ofxBankAccount represents the struct of open financial exchange (ofx) bank account
119+
type ofxBankAccount struct {
120+
BankId string `xml:"BANKID"`
121+
BranchId string `xml:"BRANCHID"`
122+
AccountId string `xml:"ACCTID"`
123+
AccountType ofxAccountType `xml:"ACCTTYPE"`
124+
AccountKey string `xml:"ACCTKEY"`
125+
}
126+
127+
// ofxCreditCardAccount represents the struct of open financial exchange (ofx) credit card account
128+
type ofxCreditCardAccount struct {
129+
AccountId string `xml:"ACCTID"`
130+
AccountKey string `xml:"ACCTKEY"`
131+
}
132+
133+
// ofxBankTransactionList represents the struct of open financial exchange (ofx) bank transaction list
134+
type ofxBankTransactionList struct {
135+
StartDate string `xml:"DTSTART"`
136+
EndDate string `xml:"DTEND"`
137+
StatementTransactions []*ofxBankStatementTransaction `xml:"STMTTRN"`
138+
}
139+
140+
// ofxCreditCardTransactionList represents the struct of open financial exchange (ofx) credit card transaction list
141+
type ofxCreditCardTransactionList struct {
142+
StartDate string `xml:"DTSTART"`
143+
EndDate string `xml:"DTEND"`
144+
StatementTransactions []*ofxCreditCardStatementTransaction `xml:"STMTTRN"`
145+
}
146+
147+
// ofxBasicStatementTransaction represents the struct of open financial exchange (ofx) basic statement transaction
148+
type ofxBasicStatementTransaction struct {
149+
TransactionId string `xml:"FITID"`
150+
TransactionType ofxTransactionType `xml:"TRNTYPE"`
151+
PostedDate string `xml:"DTPOSTED"`
152+
Amount string `xml:"TRNAMT"`
153+
Name string `xml:"NAME"`
154+
Payee *ofxPayee `xml:"PAYEE"`
155+
Memo string `xml:"MEMO"`
156+
Currency string `xml:"CURRENCY"`
157+
OriginalCurrency string `xml:"ORIGCURRENCY"`
158+
}
159+
160+
// ofxBankStatementTransaction represents the struct of open financial exchange (ofx) bank statement transaction
161+
type ofxBankStatementTransaction struct {
162+
ofxBasicStatementTransaction
163+
AccountTo *ofxCreditCardAccount `xml:"BANKACCTTO"`
164+
}
165+
166+
// ofxCreditCardStatementTransaction represents the struct of open financial exchange (ofx) credit card statement transaction
167+
type ofxCreditCardStatementTransaction struct {
168+
ofxBasicStatementTransaction
169+
AccountTo *ofxCreditCardAccount `xml:"CCACCTTO"`
170+
}
171+
172+
// ofxPayee represents the struct of open financial exchange (ofx) payee info
173+
type ofxPayee struct {
174+
Name string `xml:"NAME"`
175+
Address1 string `xml:"ADDR1"`
176+
Address2 string `xml:"ADDR2"`
177+
Address3 string `xml:"ADDR3"`
178+
City string `xml:"CITY"`
179+
State string `xml:"STATE"`
180+
PostalCode string `xml:"POSTALCODE"`
181+
Country string `xml:"COUNTRY"`
182+
Phone string `xml:"PHONE"`
183+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package ofx
2+
3+
import (
4+
"bytes"
5+
"encoding/xml"
6+
7+
"github.com/mayswind/ezbookkeeping/pkg/core"
8+
"github.com/mayswind/ezbookkeeping/pkg/errs"
9+
"github.com/mayswind/ezbookkeeping/pkg/utils"
10+
)
11+
12+
// ofxFileReader defines the structure of open financial exchange (ofx) file reader
13+
type ofxFileReader struct {
14+
xmlDecoder *xml.Decoder
15+
}
16+
17+
// read returns the imported open financial exchange (ofx) file
18+
func (r *ofxFileReader) read(ctx core.Context) (*ofxFile, error) {
19+
file := &ofxFile{}
20+
21+
err := r.xmlDecoder.Decode(&file)
22+
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
return file, nil
28+
}
29+
30+
func createNewOFXFileReader(data []byte) (*ofxFileReader, error) {
31+
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
32+
xmlDecoder := xml.NewDecoder(bytes.NewReader(data))
33+
xmlDecoder.CharsetReader = utils.IdentReader
34+
35+
return &ofxFileReader{
36+
xmlDecoder: xmlDecoder,
37+
}, nil
38+
} else if len(data) > 13 && string(data[0:13]) == "OFXHEADER:100" { // ofx 1.x starts with OFXHEADER:100
39+
40+
} else if len(data) > 5 && string(data[0:5]) == "<OFX>" { // no ofx header
41+
xmlDecoder := xml.NewDecoder(bytes.NewReader(data))
42+
xmlDecoder.CharsetReader = utils.IdentReader
43+
44+
return &ofxFileReader{
45+
xmlDecoder: xmlDecoder,
46+
}, nil
47+
}
48+
49+
return nil, errs.ErrInvalidOFXFile
50+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package ofx
2+
3+
import (
4+
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
5+
"github.com/mayswind/ezbookkeeping/pkg/core"
6+
"github.com/mayswind/ezbookkeeping/pkg/models"
7+
"github.com/mayswind/ezbookkeeping/pkg/utils"
8+
)
9+
10+
var ofxTransactionTypeNameMapping = map[models.TransactionType]string{
11+
models.TRANSACTION_TYPE_INCOME: utils.IntToString(int(models.TRANSACTION_TYPE_INCOME)),
12+
models.TRANSACTION_TYPE_EXPENSE: utils.IntToString(int(models.TRANSACTION_TYPE_EXPENSE)),
13+
models.TRANSACTION_TYPE_TRANSFER: utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER)),
14+
}
15+
16+
// ofxTransactionDataImporter defines the structure of open financial exchange (ofx) file importer for transaction data
17+
type ofxTransactionDataImporter struct {
18+
}
19+
20+
// Initialize a open financial exchange (ofx) transaction data importer singleton instance
21+
var (
22+
OFXTransactionDataImporter = &ofxTransactionDataImporter{}
23+
)
24+
25+
// ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data
26+
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) {
27+
ofxDataReader, err := createNewOFXFileReader(data)
28+
29+
if err != nil {
30+
return nil, nil, nil, nil, nil, nil, err
31+
}
32+
33+
ofxFile, err := ofxDataReader.read(ctx)
34+
35+
if err != nil {
36+
return nil, nil, nil, nil, nil, nil, err
37+
}
38+
39+
transactionDataTable, err := createNewOFXTransactionDataTable(ofxFile)
40+
41+
if err != nil {
42+
return nil, nil, nil, nil, nil, nil, err
43+
}
44+
45+
dataTableImporter := datatable.CreateNewSimpleImporter(ofxTransactionTypeNameMapping)
46+
47+
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
48+
}

0 commit comments

Comments
 (0)