mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-15 23:47:33 +08:00
import transactions from iif file
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
package iif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
)
|
||||
|
||||
const iifAccountSampleLineSignColumnName = "!ACCNT"
|
||||
const iifTransactionSampleLineSignColumnName = "!TRNS"
|
||||
const iifTransactionSplitSampleLineSignColumnName = "!SPL"
|
||||
const iifTransactionEndSampleLineSignColumnName = "!ENDTRNS"
|
||||
|
||||
const iifAccountLineSignColumnName = "ACCNT"
|
||||
const iifTransactionLineSignColumnName = "TRNS"
|
||||
const iifTransactionSplitLineSignColumnName = "SPL"
|
||||
const iifTransactionEndLineSignColumnName = "ENDTRNS"
|
||||
|
||||
// iifDataReader defines the structure of intuit interchange format (iif) data reader
|
||||
type iifDataReader struct {
|
||||
reader *csv.Reader
|
||||
}
|
||||
|
||||
// read returns the iif transaction dataset
|
||||
func (r *iifDataReader) read(ctx core.Context) ([]*iifAccountDataset, []*iifTransactionDataset, error) {
|
||||
allAccountDatasets := make([]*iifAccountDataset, 0)
|
||||
allTransactionDatasets := make([]*iifTransactionDataset, 0)
|
||||
|
||||
currentDatasetType := ""
|
||||
lastLineSign := ""
|
||||
|
||||
var currentAccountDataset *iifAccountDataset
|
||||
var currentTransactionDataset *iifTransactionDataset
|
||||
var currentTransactionData *iifTransactionData
|
||||
|
||||
for {
|
||||
items, err := r.reader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] cannot parse tsv data, because %s", err.Error())
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if len(items) == 1 && items[0] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(items[0]) < 1 {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] line first column is empty")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if items[0][0] == '!' { // sample line
|
||||
if lastLineSign != "" {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif missing transaction end line")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if currentAccountDataset != nil {
|
||||
allAccountDatasets = append(allAccountDatasets, currentAccountDataset)
|
||||
currentAccountDataset = nil
|
||||
}
|
||||
|
||||
if currentTransactionDataset != nil {
|
||||
allTransactionDatasets = append(allTransactionDatasets, currentTransactionDataset)
|
||||
currentTransactionDataset = nil
|
||||
}
|
||||
|
||||
if items[0] == iifTransactionSplitSampleLineSignColumnName || items[0] == iifTransactionEndSampleLineSignColumnName {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] read transaction split sample line or transaction end sample line sign before transaction sample line sign")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
} else {
|
||||
currentDatasetType = items[0]
|
||||
lastLineSign = ""
|
||||
}
|
||||
}
|
||||
|
||||
if currentDatasetType == "" {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] cannot read data line before sample line")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
} else if currentDatasetType == iifAccountSampleLineSignColumnName {
|
||||
if currentAccountDataset == nil {
|
||||
currentAccountDataset, err = r.readAccountSampleLine(ctx, items)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
if items[0] == iifAccountLineSignColumnName {
|
||||
accountData := &iifAccountData{
|
||||
dataItems: items,
|
||||
}
|
||||
currentAccountDataset.accounts = append(currentAccountDataset.accounts, accountData)
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif line expected reading account sign, but actual is \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
}
|
||||
} else if currentDatasetType == iifTransactionSampleLineSignColumnName {
|
||||
if currentTransactionDataset == nil {
|
||||
currentTransactionDataset, err = r.readTransactionSampleLines(ctx, items)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
if lastLineSign == "" {
|
||||
if items[0] == iifTransactionLineSignColumnName {
|
||||
currentTransactionData = &iifTransactionData{
|
||||
dataItems: items,
|
||||
splitData: make([]*iifTransactionSplitData, 0),
|
||||
}
|
||||
lastLineSign = items[0]
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif line expected reading transaction sign, but actual is \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
} else if lastLineSign == iifTransactionLineSignColumnName || lastLineSign == iifTransactionSplitLineSignColumnName {
|
||||
if items[0] == iifTransactionSplitLineSignColumnName {
|
||||
currentTransactionData.splitData = append(currentTransactionData.splitData, &iifTransactionSplitData{
|
||||
dataItems: items,
|
||||
})
|
||||
lastLineSign = items[0]
|
||||
} else if items[0] == iifTransactionEndLineSignColumnName {
|
||||
currentTransactionDataset.transactions = append(currentTransactionDataset.transactions, currentTransactionData)
|
||||
lastLineSign = ""
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif line expected reading split sign or transaction end sign, but actual is \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif missing transaction sample end line")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lastLineSign != "" {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] iif missing transaction end line")
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if currentAccountDataset != nil {
|
||||
allAccountDatasets = append(allAccountDatasets, currentAccountDataset)
|
||||
}
|
||||
|
||||
if currentTransactionDataset != nil {
|
||||
allTransactionDatasets = append(allTransactionDatasets, currentTransactionDataset)
|
||||
}
|
||||
|
||||
return allAccountDatasets, allTransactionDatasets, nil
|
||||
}
|
||||
|
||||
func (r *iifDataReader) readAccountSampleLine(ctx core.Context, items []string) (*iifAccountDataset, error) {
|
||||
accountSampleItems := items
|
||||
accountDataColumnIndexes := make(map[string]int, len(accountSampleItems))
|
||||
|
||||
for i := 1; i < len(accountSampleItems); i++ {
|
||||
columnName := accountSampleItems[i]
|
||||
accountDataColumnIndexes[columnName] = i
|
||||
}
|
||||
|
||||
return &iifAccountDataset{
|
||||
accountDataColumnIndexes: accountDataColumnIndexes,
|
||||
accounts: make([]*iifAccountData, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *iifDataReader) readTransactionSampleLines(ctx core.Context, items []string) (*iifTransactionDataset, error) {
|
||||
transactionSampleItems := items
|
||||
transactionDataColumnIndexes := make(map[string]int, len(transactionSampleItems))
|
||||
|
||||
for i := 1; i < len(transactionSampleItems); i++ {
|
||||
columnName := transactionSampleItems[i]
|
||||
transactionDataColumnIndexes[columnName] = i
|
||||
}
|
||||
|
||||
splitSampleItems, err := r.reader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
log.Errorf(ctx, "[iif_data_reader.readTransactionSampleLines] expected reading transaction split sample line, but read eof")
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if len(splitSampleItems) < 1 || splitSampleItems[0] != iifTransactionSplitSampleLineSignColumnName {
|
||||
log.Errorf(ctx, "[iif_data_reader.readTransactionSampleLines] expected reading transaction split sample line, but read \"%s\"", strings.Join(splitSampleItems, "\t"))
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
splitDataColumnIndexes := make(map[string]int, len(splitSampleItems))
|
||||
|
||||
for i := 1; i < len(splitSampleItems); i++ {
|
||||
columnName := splitSampleItems[i]
|
||||
splitDataColumnIndexes[columnName] = i
|
||||
}
|
||||
|
||||
transactionEndSampleItems, err := r.reader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
log.Errorf(ctx, "[iif_data_reader.readTransactionSampleLines] expected reading transaction end sample line, but read eof")
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if len(transactionEndSampleItems) < 1 || transactionEndSampleItems[0] != iifTransactionEndSampleLineSignColumnName {
|
||||
log.Errorf(ctx, "[iif_data_reader.readTransactionSampleLines] expected reading transaction end sample line, but read \"%s\"", strings.Join(splitSampleItems, "\t"))
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
return &iifTransactionDataset{
|
||||
transactionDataColumnIndexes: transactionDataColumnIndexes,
|
||||
splitDataColumnIndexes: splitDataColumnIndexes,
|
||||
transactions: make([]*iifTransactionData, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createNewIifDataReader(data []byte) *iifDataReader {
|
||||
reader := bytes.NewReader(data)
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.Comma = '\t'
|
||||
csvReader.FieldsPerRecord = -1
|
||||
|
||||
return &iifDataReader{
|
||||
reader: csvReader,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user