support custom script to process delimiter-separated values (data) file / data

This commit is contained in:
MaysWind
2025-09-15 00:21:13 +08:00
parent 538d2b8205
commit c75fdfea1c
20 changed files with 1007 additions and 13 deletions
@@ -0,0 +1,96 @@
package _default
import (
"encoding/json"
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
var allJsonDataSupportedColumns = []datatable.TransactionDataTableColumn{
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME,
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE,
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE,
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY,
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME,
datatable.TRANSACTION_DATA_TABLE_AMOUNT,
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME,
datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT,
datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION,
datatable.TRANSACTION_DATA_TABLE_TAGS,
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION,
}
// defaultTransactionDataJsonImporter defines the structure of ezbookkeeping default json importer for transaction data
type defaultTransactionDataJsonImporter struct{}
// Initialize an ezbookkeeping default transaction data json file importer singleton instance
var (
DefaultTransactionDataJsonFileImporter = &defaultTransactionDataJsonImporter{}
)
// ParseImportedData returns the imported data by parsing the transaction json data
func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
var importRequest models.ImportTransactionRequest
if err := json.Unmarshal(data, &importRequest); err != nil {
return nil, nil, nil, nil, nil, nil, errs.ErrInvalidJSONFile
}
transactionDataTable, err := c.createNewDefaultTransactionDataTable(importRequest)
if err != nil {
return nil, nil, nil, nil, nil, nil, err
}
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(
ezbookkeepingTransactionTypeNameMapping,
ezbookkeepingGeoLocationSeparator,
ezbookkeepingGeoLocationOrder,
ezbookkeepingTagSeparator,
)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
}
func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTable(importRequest models.ImportTransactionRequest) (datatable.TransactionDataTable, error) {
transactionDataTable := datatable.CreateNewWritableTransactionDataTable(allJsonDataSupportedColumns)
if importRequest.Transactions == nil || len(importRequest.Transactions) < 1 {
return nil, errs.ErrNotFoundTransactionDataInFile
}
for i := 0; i < len(importRequest.Transactions); i++ {
transaction := importRequest.Transactions[i]
utcOffset, err := utils.StringToInt(transaction.UtcOffset)
if err != nil {
return nil, errs.ErrTransactionTimeZoneInvalid
}
timezone := time.FixedZone("Transaction Timezone", utcOffset*60)
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(timezone)
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
row[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = transaction.SourceAmount
row[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = transaction.DestinationAccountName
row[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = transaction.DestinationAmount
row[datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION] = transaction.GeoLocation
row[datatable.TRANSACTION_DATA_TABLE_TAGS] = transaction.TagNames
row[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = transaction.Comment
transactionDataTable.Add(row)
}
return transactionDataTable, nil
}
@@ -38,6 +38,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
return _default.DefaultTransactionDataCSVFileConverter, nil
} else if fileType == "ezbookkeeping_tsv" {
return _default.DefaultTransactionDataTSVFileConverter, nil
} else if fileType == "ezbookkeeping_json" {
return _default.DefaultTransactionDataJsonFileImporter, nil
} else if fileType == "ofx" {
return ofx.OFXTransactionDataImporter, nil
} else if fileType == "qfx" {
+1
View File
@@ -30,4 +30,5 @@ var (
ErrInvalidAmountExpression = NewNormalError(NormalSubcategoryConverter, 23, http.StatusBadRequest, "invalid amount expression")
ErrInvalidXmlFile = NewNormalError(NormalSubcategoryConverter, 24, http.StatusBadRequest, "invalid xml file")
ErrInvalidMT940File = NewNormalError(NormalSubcategoryConverter, 25, http.StatusBadRequest, "invalid mt940 file")
ErrInvalidJSONFile = NewNormalError(NormalSubcategoryConverter, 26, http.StatusBadRequest, "invalid json file")
)
+20
View File
@@ -14,6 +14,26 @@ type ImportTransaction struct {
OriginalTagNames []string
}
// ImportTransactionRequest represents all parameters of the imported transaction data
type ImportTransactionRequest struct {
Transactions []*ImportTransactionRequestItem
}
// ImportTransactionRequestItem represents a single item of the imported transaction data
type ImportTransactionRequestItem struct {
Time string `json:"time"`
UtcOffset string `json:"utcOffset"`
Type string `json:"type"`
CategoryName string `json:"categoryName,omitempty"`
SourceAccountName string `json:"sourceAccountName,omitempty"`
DestinationAccountName string `json:"destinationAccountName,omitempty"`
SourceAmount string `json:"sourceAmount"`
DestinationAmount string `json:"destinationAmount,omitempty"`
GeoLocation string `json:"geoLocation,omitempty"`
TagNames string `json:"tagNames,omitempty"`
Comment string `json:"comment,omitempty"`
}
// ImportTransactionResponse represents a view-object of the imported transaction data
type ImportTransactionResponse struct {
Type TransactionType `json:"type"`