use the daylight saving time zone as default time zone rather than the current standard time zone during the DST

This commit is contained in:
MaysWind
2025-12-24 00:33:47 +08:00
parent c35cbbda15
commit 76af5d946a
96 changed files with 1179 additions and 882 deletions
+6 -6
View File
@@ -150,10 +150,10 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
_, utcOffset, err := c.GetClientTimezone() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -278,7 +278,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
} }
} }
err = a.accounts.CreateAccounts(c, mainAccount, accountCreateReq.BalanceTime, childrenAccounts, childrenAccountBalanceTimes, utcOffset) err = a.accounts.CreateAccounts(c, mainAccount, accountCreateReq.BalanceTime, childrenAccounts, childrenAccountBalanceTimes, clientTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error()) log.Errorf(c, "[accounts.AccountCreateHandler] failed to create account \"id:%d\" for user \"uid:%d\", because %s", mainAccount.AccountId, uid, err.Error())
@@ -315,10 +315,10 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
return nil, errs.ErrAccountIdInvalid return nil, errs.ErrAccountIdInvalid
} }
_, utcOffset, err := c.GetClientTimezone() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -521,7 +521,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
} }
} }
err = a.accounts.ModifyAccounts(c, mainAccount, toUpdateAccounts, toAddAccounts, toAddAccountBalanceTimes, toDeleteAccountIds, utcOffset) err = a.accounts.ModifyAccounts(c, mainAccount, toUpdateAccounts, toAddAccounts, toAddAccountBalanceTimes, toDeleteAccountIds, clientTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error()) log.Errorf(c, "[accounts.AccountModifyHandler] failed to update account \"id:%d\" for user \"uid:%d\", because %s", accountModifyReq.Id, uid, err.Error())
+6 -6
View File
@@ -302,11 +302,11 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
timezone, _, err := c.GetClientTimezone() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone, because %s", err.Error())
timezone = time.Local clientTimezone = time.Local
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
@@ -413,13 +413,13 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
return nil, "", errs.Or(err, errs.ErrOperationFailed) return nil, "", errs.Or(err, errs.ErrOperationFailed)
} }
fileName := a.getFileName(user, timezone, fileType) fileName := a.getFileName(user, clientTimezone, fileType)
return result, fileName, nil return result, fileName, nil
} }
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location, fileExtension string) string { func (a *DataManagementsApi) getFileName(user *models.User, clientTimezone *time.Location, fileExtension string) string {
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone) currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), clientTimezone)
currentTime = strings.Replace(currentTime, "-", "_", -1) currentTime = strings.Replace(currentTime, "-", "_", -1)
currentTime = strings.Replace(currentTime, " ", "_", -1) currentTime = strings.Replace(currentTime, " ", "_", -1)
currentTime = strings.Replace(currentTime, ":", "_", -1) currentTime = strings.Replace(currentTime, ":", "_", -1)
+4 -4
View File
@@ -47,7 +47,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
return nil, errs.ErrLargeLanguageModelProviderNotEnabled return nil, errs.ErrLargeLanguageModelProviderNotEnabled
} }
clientTimezone, utcOffset, err := c.GetClientTimezone() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone, because %s", err.Error()) log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone, because %s", err.Error())
@@ -238,10 +238,10 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
return a.parseRecognizedReceiptImageResponse(c, uid, utcOffset, result, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return a.parseRecognizedReceiptImageResponse(c, uid, clientTimezone, result, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.WebContext, uid int64, utcOffset int16, recognizedResult *models.RecognizedReceiptImageResult, 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.RecognizedReceiptImageResponse, *errs.Error) { func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.WebContext, uid int64, clientTimezone *time.Location, recognizedResult *models.RecognizedReceiptImageResult, 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.RecognizedReceiptImageResponse, *errs.Error) {
recognizedReceiptImageResponse := &models.RecognizedReceiptImageResponse{ recognizedReceiptImageResponse := &models.RecognizedReceiptImageResponse{
Type: models.TRANSACTION_TYPE_EXPENSE, Type: models.TRANSACTION_TYPE_EXPENSE,
} }
@@ -290,7 +290,7 @@ func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.Web
if len(recognizedResult.Time) > 0 { if len(recognizedResult.Time) > 0 {
longDateTime := a.getLongDateTime(recognizedResult.Time) longDateTime := a.getLongDateTime(recognizedResult.Time)
timestamp, err := utils.ParseFromLongDateTime(longDateTime, utcOffset) timestamp, err := utils.ParseFromLongDateTimeInTimeZone(longDateTime, clientTimezone)
if err != nil { if err != nil {
log.Warnf(c, "[large_language_models.parseRecognizedReceiptImageResponse] recoginzed time \"%s\" is invalid", recognizedResult.Time) log.Warnf(c, "[large_language_models.parseRecognizedReceiptImageResponse] recoginzed time \"%s\" is invalid", recognizedResult.Time)
+3 -3
View File
@@ -1488,10 +1488,10 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
return nil, errs.ErrParameterInvalid return nil, errs.ErrParameterInvalid
} }
_, utcOffset, err := c.GetClientTimezone() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -1688,7 +1688,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
tagMap := a.transactionTags.GetVisibleTagNameMapByList(tags) tagMap := a.transactionTags.GetVisibleTagNameMapByList(tags)
parsedTransactions, _, _, _, _, _, err := dataImporter.ParseImportedData(c, user, fileData, utcOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) parsedTransactions, _, _, _, _, _, err := dataImporter.ParseImportedData(c, user, fileData, clientTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionParseImportFileHandler] failed to parse imported data for user \"uid:%d\", because %s", user.Uid, err.Error()) log.Errorf(c, "[transactions.TransactionParseImportFileHandler] failed to parse imported data for user \"uid:%d\", because %s", user.Uid, err.Error())
+1 -1
View File
@@ -819,7 +819,7 @@ func (l *UserDataCli) ImportTransaction(c *core.CliContext, username string, fil
return err return err
} }
parsedTransactions, newAccounts, newSubExpenseCategories, newSubIncomeCategories, newSubTransferCategories, newTags, err := dataImporter.ParseImportedData(c, user, data, utils.GetTimezoneOffsetMinutes(time.Local), converter.DefaultImporterOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) parsedTransactions, newAccounts, newSubExpenseCategories, newSubIncomeCategories, newSubTransferCategories, newTags, err := dataImporter.ParseImportedData(c, user, data, time.Local, converter.DefaultImporterOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
if err != nil { if err != nil {
log.CliErrorf(c, "[user_data.ImportTransaction] failed to parse imported data for \"%s\", because %s", username, err.Error()) log.CliErrorf(c, "[user_data.ImportTransaction] failed to parse imported data for \"%s\", because %s", username, err.Error())
@@ -2,6 +2,7 @@ package alipay
import ( import (
"bytes" "bytes"
"time"
"golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform" "golang.org/x/text/transform"
@@ -53,7 +54,7 @@ type alipayTransactionDataCsvFileImporter struct {
} }
// ParseImportedData returns the imported data by parsing the alipay transaction csv data // ParseImportedData returns the imported data by parsing the alipay transaction csv data
func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
enc := simplifiedchinese.GB18030 enc := simplifiedchinese.GB18030
reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder()) reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder())
@@ -83,5 +84,5 @@ func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Contex
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, alipayTransactionSupportedColumns, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, alipayTransactionSupportedColumns, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(alipayTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(alipayTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -36,7 +36,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, len(allNewTransactions)) assert.Equal(t, 4, len(allNewTransactions))
@@ -113,7 +113,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid) assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -133,7 +133,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid) assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -164,7 +164,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvestmentRefundTransaction
"2024-09-01 01:00:00,Test Account2,xxx-买入,不计收支,0.01,Test Account,退款成功,\n" + "2024-09-01 01:00:00,Test Account2,xxx-买入,不计收支,0.01,Test Account,退款成功,\n" +
"2024-09-01 02:00:00,Test Account2,xxx-买入退款,不计收支,0.01,Test Account,退款成功,\n") "2024-09-01 02:00:00,Test Account2,xxx-买入退款,不计收支,0.01,Test Account,退款成功,\n")
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -202,7 +202,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" + data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
@@ -214,7 +214,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -236,7 +236,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -259,7 +259,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -275,7 +275,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -291,7 +291,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -308,7 +308,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -325,7 +325,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -342,7 +342,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -359,7 +359,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -389,7 +389,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
"2024-09-01 23:59:59,Test Category3,充值-普通充值,不计收支,0.05,交易成功,\n") "2024-09-01 23:59:59,Test Category3,充值-普通充值,不计收支,0.05,交易成功,\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(allNewTransactions)) assert.Equal(t, 3, len(allNewTransactions))
@@ -435,7 +435,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T
"2024-09-01 08:00:00,Test Account4,信用卡还款,不计收支,0.01,Test Account,还款成功,repayment,\n") "2024-09-01 08:00:00,Test Account4,信用卡还款,不计收支,0.01,Test Account,还款成功,repayment,\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 9, len(allNewTransactions)) assert.Equal(t, 9, len(allNewTransactions))
@@ -547,7 +547,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -562,7 +562,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -587,7 +587,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransa
"2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" + "2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -608,7 +608,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransa
"2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" + "2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -629,7 +629,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *
"2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" + "2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -648,10 +648,10 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T)
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
} }
@@ -672,7 +672,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"金额(元),收/支 ,交易状态 ,\n" + "金额(元),收/支 ,交易状态 ,\n" +
"0.12 ,收入 ,交易成功 ,\n" + "0.12 ,收入 ,交易成功 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
@@ -683,7 +683,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,收/支 ,交易状态 ,\n" + "交易创建时间 ,收/支 ,交易状态 ,\n" +
"2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" + "2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Status Column // Missing Status Column
@@ -694,7 +694,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,金额(元),收/支 ,\n" + "交易创建时间 ,金额(元),收/支 ,\n" +
"2024-09-01 12:34:56 ,0.12 ,收入 ,\n" + "2024-09-01 12:34:56 ,0.12 ,收入 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
@@ -705,7 +705,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,金额(元),交易状态 ,\n" + "交易创建时间 ,金额(元),交易状态 ,\n" +
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" + "2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -724,6 +724,6 @@ func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T)
"---------------------------------交易记录明细列表------------------------------------\n" + "---------------------------------交易记录明细列表------------------------------------\n" +
"交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" +
"------------------------------------------------------------------------------------\n") "------------------------------------------------------------------------------------\n")
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -1,6 +1,8 @@
package beancount package beancount
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -24,7 +26,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the Beancount transaction data // ParseImportedData returns the imported data by parsing the Beancount transaction data
func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
beancountDataReader, err := createNewBeancountDataReader(ctx, data) beancountDataReader, err := createNewBeancountDataReader(ctx, data)
if err != nil { if err != nil {
@@ -45,5 +47,5 @@ func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, u
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(beancountTransactionTypeNameMapping, "", "", BEANCOUNT_TRANSACTION_TAG_SEPARATOR) dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(beancountTransactionTypeNameMapping, "", "", BEANCOUNT_TRANSACTION_TAG_SEPARATOR)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package beancount
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -33,7 +34,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
" Expenses:TestCategory2 1.00 CNY\n"+ " Expenses:TestCategory2 1.00 CNY\n"+
"2024-09-04 *\n"+ "2024-09-04 *\n"+
" Assets:TestAccount -0.05 CNY\n"+ " Assets:TestAccount -0.05 CNY\n"+
" Assets:TestAccount2 0.05 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount2 0.05 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -112,7 +113,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
" Assets:TestAccount -1.00 CNY\n"+ " Assets:TestAccount -1.00 CNY\n"+
"2024-09-04 *\n"+ "2024-09-04 *\n"+
" Assets:TestAccount2 0.05 CNY\n"+ " Assets:TestAccount2 0.05 CNY\n"+
" Assets:TestAccount -0.05 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount -0.05 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -182,7 +183,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024/09/01 *\n"+ "2024/09/01 *\n"+
" Equity:Opening-Balances -123.45 CNY\n"+ " Equity:Opening-Balances -123.45 CNY\n"+
" Assets:TestAccount 123.45 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -198,7 +199,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-01 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Assets:TestAccount -0.12 USD\n"+ " Assets:TestAccount -0.12 USD\n"+
" Assets:TestAccount2 0.84 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount2 0.84 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -234,13 +235,13 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 *\n"+ "2024-09-01 *\n"+
" Equity:Opening-Balances -abc CNY\n"+ " Equity:Opening-Balances -abc CNY\n"+
" Assets:TestAccount abc CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount abc CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 *\n"+ "2024-09-01 *\n"+
" Equity:Opening-Balances -1/0 CNY\n"+ " Equity:Opening-Balances -1/0 CNY\n"+
" Assets:TestAccount 1/0 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount 1/0 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -259,7 +260,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
" Assets:TestAccount 123.45 CNY\n"+ " Assets:TestAccount 123.45 CNY\n"+
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Income:TestCategory -0.12 CNY\n"+ " Income:TestCategory -0.12 CNY\n"+
" Assets:TestAccount 0.12 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount 0.12 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -281,25 +282,25 @@ func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Assets:TestAccount 0.11 CNY\n"+ " Assets:TestAccount 0.11 CNY\n"+
" Assets:TestAccount2 0.11 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message) assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Expenses:TestCategory -0.11 CNY\n"+ " Expenses:TestCategory -0.11 CNY\n"+
" Expenses:TestCategory2 0.11 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Expenses:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message) assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Income:TestCategory -0.11 CNY\n"+ " Income:TestCategory -0.11 CNY\n"+
" Income:TestCategory2 0.11 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Income:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message) assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Equity:TestCategory -0.11 CNY\n"+ " Equity:TestCategory -0.11 CNY\n"+
" Equity:TestCategory2 0.11 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Equity:TestCategory2 0.11 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message) assert.EqualError(t, err, errs.ErrThereAreNotSupportedTransactionType.Message)
} }
@@ -316,7 +317,7 @@ func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitT
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+ "2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Assets:TestAccount -0.23 CNY\n"+ " Assets:TestAccount -0.23 CNY\n"+
" Assets:TestAccount2 0.11 CNY\n"+ " Assets:TestAccount2 0.11 CNY\n"+
" Assets:TestAccount3 0.12 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount3 0.12 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message) assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
} }
@@ -333,27 +334,27 @@ func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequire
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"* \"narration\"\n"+ "* \"narration\"\n"+
" Equity:Opening-Balances -123.45 CNY\n"+ " Equity:Opening-Balances -123.45 CNY\n"+
" Assets:TestAccount 123.45 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
// Missing Account Name // Missing Account Name
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\n"+ "2024-09-01 * \"narration\"\n"+
" Equity:Opening-Balances -123.45 CNY\n"+ " Equity:Opening-Balances -123.45 CNY\n"+
" 123.45 CNY\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " 123.45 CNY\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message) assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
// Missing Amount // Missing Amount
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\n"+ "2024-09-01 * \"narration\"\n"+
" Equity:Opening-Balances\n"+ " Equity:Opening-Balances\n"+
" Assets:TestAccount\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message) assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
// Missing Commodity // Missing Commodity
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\n"+ "2024-09-01 * \"narration\"\n"+
" Equity:Opening-Balances -123.45\n"+ " Equity:Opening-Balances -123.45\n"+
" Assets:TestAccount 123.45\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) " Assets:TestAccount 123.45\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message) assert.EqualError(t, err, errs.ErrInvalidBeancountFile.Message)
} }
@@ -231,7 +231,7 @@ func (t *camtStatementTransactionDataRowIterator) parseTransaction(ctx core.Cont
} }
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location()) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location()) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
} else if entry.BookingDate != nil && entry.BookingDate.Date != "" { } else if entry.BookingDate != nil && entry.BookingDate.Date != "" {
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = fmt.Sprintf("%s 00:00:00", entry.BookingDate.Date) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = fmt.Sprintf("%s 00:00:00", entry.BookingDate.Date)
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE
@@ -1,6 +1,8 @@
package camt package camt
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -23,7 +25,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the camt.053 file transaction data // ParseImportedData returns the imported data by parsing the camt.053 file transaction data
func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
camt053DataReader, err := createNewCamt053FileReader(data) camt053DataReader, err := createNewCamt053FileReader(data)
if err != nil { if err != nil {
@@ -44,5 +46,5 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(camtTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(camtTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package camt
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -65,7 +66,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -158,7 +159,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(allNewTransactions)) assert.Equal(t, 3, len(allNewTransactions))
@@ -197,7 +198,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -220,7 +221,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -243,7 +244,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -266,7 +267,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -315,7 +316,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -366,7 +367,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -404,7 +405,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -431,7 +432,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -468,7 +469,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -499,7 +500,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -530,7 +531,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -573,7 +574,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -608,7 +609,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -635,7 +636,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -665,7 +666,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingAccountData.Message) assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
} }
@@ -695,7 +696,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message) assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -717,7 +718,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -739,7 +740,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -761,6 +762,6 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry> </Ntry>
</Stmt> </Stmt>
</BkToCstmrStmt> </BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) </Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -28,10 +28,11 @@ func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context
} }
dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15) dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15)
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone) dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(transactionUnixTime, transactionTimeZone)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone) dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionUnixTime, transactionTimeZone)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataTableBuilder.ReplaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type)) dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataTableBuilder.ReplaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type))
dataRowMap[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap) dataRowMap[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap) dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
@@ -3,6 +3,7 @@ package converter
import ( import (
"sort" "sort"
"strings" "strings"
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
@@ -29,7 +30,7 @@ type DataTableTransactionDataImporter struct {
} }
// ParseImportedData returns the imported transaction data // ParseImportedData returns the imported transaction data
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezoneOffset int16, additionalOptions TransactionDataImporterOptions, 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) { func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezone *time.Location, additionalOptions TransactionDataImporterOptions, 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) {
if dataTable.TransactionRowCount() < 1 { if dataTable.TransactionRowCount() < 1 {
log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid) log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid)
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
@@ -94,7 +95,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
continue continue
} }
timezoneOffset := defaultTimezoneOffset timezone := defaultTimezone
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) && if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) &&
dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) != datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE { dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) != datatable.TRANSACTION_DATA_TABLE_TIMEZONE_NOT_AVAILABLE {
@@ -105,10 +106,10 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTimeZoneInvalid return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTimeZoneInvalid
} }
timezoneOffset = utils.GetTimezoneOffsetMinutes(transactionTimezone) timezone = transactionTimezone
} }
transactionTime, err := utils.ParseFromLongDateTime(dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), timezoneOffset) transactionTime, err := utils.ParseFromLongDateTimeInTimeZone(dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), timezone)
if err != nil { if err != nil {
log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse time \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), dataRowIndex, user.Uid, err.Error()) log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse time \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME), dataRowIndex, user.Uid, err.Error())
@@ -373,7 +374,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
Type: transactionDbType, Type: transactionDbType,
CategoryId: categoryId, CategoryId: categoryId,
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()), TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
TimezoneUtcOffset: timezoneOffset, TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), timezone),
AccountId: account.AccountId, AccountId: account.AccountId,
Amount: amount, Amount: amount,
HideAmount: false, HideAmount: false,
@@ -1,6 +1,8 @@
package converter package converter
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
) )
@@ -14,7 +16,7 @@ type TransactionDataExporter interface {
// TransactionDataImporter defines the structure of transaction data importer // TransactionDataImporter defines the structure of transaction data importer
type TransactionDataImporter interface { type TransactionDataImporter interface {
// ParseImportedData returns the imported data // ParseImportedData returns the imported data
ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions TransactionDataImporterOptions, 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) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions TransactionDataImporterOptions, 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)
} }
// TransactionDataConverter defines the structure of transaction data converter // TransactionDataConverter defines the structure of transaction data converter
@@ -35,7 +35,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the transaction json data // 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, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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 var importRequest models.ImportTransactionRequest
if err := json.Unmarshal(data, &importRequest); err != nil { if err := json.Unmarshal(data, &importRequest); err != nil {
@@ -55,7 +55,7 @@ func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context,
ezbookkeepingTagSeparator, ezbookkeepingTagSeparator,
) )
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTable(importRequest models.ImportTransactionRequest) (datatable.TransactionDataTable, error) { func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTable(importRequest models.ImportTransactionRequest) (datatable.TransactionDataTable, error) {
@@ -75,10 +75,11 @@ func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTabl
} }
timezone := time.FixedZone("Transaction Timezone", utcOffset*60) timezone := time.FixedZone("Transaction Timezone", utcOffset*60)
timezoneOffset := utils.FormatTimezoneOffset(time.Now().Unix(), timezone)
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns)) row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time 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_TIMEZONE] = timezoneOffset
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
@@ -1,6 +1,8 @@
package _default package _default
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
@@ -84,7 +86,7 @@ func (c *defaultTransactionDataPlainTextConverter) ToExportedContent(ctx core.Co
} }
// ParseImportedData returns the imported data by parsing the transaction plain text data // ParseImportedData returns the imported data by parsing the transaction plain text data
func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
dataTable, err := createNewDefaultPlainTextDataTable( dataTable, err := createNewDefaultPlainTextDataTable(
string(data), string(data),
c.columnSeparator, c.columnSeparator,
@@ -104,5 +106,5 @@ func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Co
ezbookkeepingTagSeparator, ezbookkeepingTagSeparator,
) )
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package _default
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -139,7 +140,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MinimumValidDat
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+ "2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+
"2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+ "2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+
"2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+ "2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+
"2024-09-01 23:59:59,Transfer,Test Category3,Test Account,0.05,Test Account2,0.05"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 23:59:59,Transfer,Test Category3,Test Account,0.05,Test Account2,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -207,11 +208,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -225,7 +226,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTyp
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
} }
@@ -239,19 +240,19 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidTimez
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,-10:00,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,-10:00,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,+00:00,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+00:00,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,+12:45,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+12:45,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -267,7 +268,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
} }
@@ -282,7 +283,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidAccou
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+ allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -309,12 +310,12 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAcc
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -328,11 +329,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNotSupport
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
"2024-09-01 01:23:45,Balance Modification,,Test Account,XXX,123.45,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 01:23:45,Balance Modification,,Test Account,XXX,123.45,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -346,11 +347,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmo
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -364,14 +365,14 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(12345), allNewTransactions[0].Amount) assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount) assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(12345), allNewTransactions[0].Amount) assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
@@ -388,7 +389,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseValidGeogr
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -406,18 +407,18 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidGeo
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude) assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude) assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message) assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message) assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
} }
@@ -431,7 +432,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseTag(t *tes
} }
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+ _, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -460,7 +461,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseDescriptio
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -476,7 +477,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHead
DefaultCurrency: "CNY", DefaultCurrency: "CNY",
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -491,31 +492,31 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequired
// Missing Time Column // Missing Time Column
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Sub Category Column // Missing Sub Category Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account Name Column // Missing Account Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,CNY,123.45,,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,CNY,123.45,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account2 Name Column // Missing Account2 Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -5,6 +5,7 @@ import (
"encoding/csv" "encoding/csv"
"io" "io"
"strings" "strings"
"time"
"golang.org/x/text/encoding" "golang.org/x/text/encoding"
"golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/charmap"
@@ -148,7 +149,7 @@ func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Contex
} }
// ParseImportedData returns the imported data by parsing the custom transaction dsv data // ParseImportedData returns the imported data by parsing the custom transaction dsv data
func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
allLines, err := c.ParseDsvFileLines(ctx, data) allLines, err := c.ParseDsvFileLines(ctx, data)
if err != nil { if err != nil {
@@ -159,7 +160,7 @@ func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Contex
transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat, c.amountDecimalSeparator, c.amountDigitGroupingSymbol) transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat, c.amountDecimalSeparator, c.amountDigitGroupingSymbol)
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.geoLocationOrder, c.transactionTagSeparator) dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.geoLocationOrder, c.transactionTagSeparator)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
// IsDelimiterSeparatedValuesFileType returns whether the file type is the delimiter-separated values file type // IsDelimiterSeparatedValuesFileType returns whether the file type is the delimiter-separated values file type
@@ -2,6 +2,7 @@ package dsv
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -92,7 +93,7 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
"2024-09-01 00:00:00,B,123.45\n"+ "2024-09-01 00:00:00,B,123.45\n"+
"2024-09-01 01:23:45,I,0.12\n"+ "2024-09-01 01:23:45,I,0.12\n"+
"2024-09-01 12:34:56,E,1.00\n"+ "2024-09-01 12:34:56,E,1.00\n"+
"2024-09-01 23:59:59,T,0.05"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 23:59:59,T,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -184,7 +185,7 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
"\"2024-09-01 00:00:00\",\"+08:00\",\"Balance Modification\",\"\",\"\",\"Test Account\",\"CNY\",\"123.45\",\"\",\"\",\"\",\"\",\"\",\"\"\n"+ "\"2024-09-01 00:00:00\",\"+08:00\",\"Balance Modification\",\"\",\"\",\"Test Account\",\"CNY\",\"123.45\",\"\",\"\",\"\",\"\",\"\",\"\"\n"+
"\"2024-09-01 01:23:45\",\"+08:00\",\"Income\",\"Test Category\",\"Test Sub Category\",\"Test Account\",\"CNY\",\"0.12\",\"\",\"\",\"\",\"123.450000 45.670000\",\"Test Tag;Test Tag2\",\"Hello World\"\n"+ "\"2024-09-01 01:23:45\",\"+08:00\",\"Income\",\"Test Category\",\"Test Sub Category\",\"Test Account\",\"CNY\",\"0.12\",\"\",\"\",\"\",\"123.450000 45.670000\",\"Test Tag;Test Tag2\",\"Hello World\"\n"+
"\"2024-09-01 12:34:56\",\"+00:00\",\"Expense\",\"Test Category2\",\"Test Sub Category2\",\"Test Account\",\"CNY\",\"1.00\",\"\",\"\",\"\",\"\",\"Test Tag\",\"Foo#Bar\"\n"+ "\"2024-09-01 12:34:56\",\"+00:00\",\"Expense\",\"Test Category2\",\"Test Sub Category2\",\"Test Account\",\"CNY\",\"1.00\",\"\",\"\",\"\",\"\",\"Test Tag\",\"Foo#Bar\"\n"+
"\"2024-09-01 23:59:59\",\"-05:00\",\"Transfer\",\"Test Category3\",\"Test Sub Category3\",\"Test Account\",\"CNY\",\"0.05\",\"Test Account2\",\"USD\",\"0.35\",\"\",\"Test Tag2\",\"foo\tbar\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"2024-09-01 23:59:59\",\"-05:00\",\"Transfer\",\"Test Category3\",\"Test Sub Category3\",\"Test Account\",\"CNY\",\"0.05\",\"Test Account2\",\"USD\",\"0.35\",\"\",\"Test Tag2\",\"foo\tbar\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -273,11 +274,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01T12:34:56,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01T12:34:56,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"09/01/2024 12:34:56,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "09/01/2024 12:34:56,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -304,7 +305,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,A,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,A,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -328,7 +329,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,B,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,B,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
} }
@@ -352,19 +353,19 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56-10:00,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56-10:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56+00:00,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56+00:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56+12:45,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56+12:45,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -390,19 +391,19 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56-1000,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56+0000,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56+1245,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -429,19 +430,19 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone(t *testing.T) {
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,-10:00,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,-10:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,+00:00,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+00:00,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,+12:45,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+12:45,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -468,19 +469,19 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone2(t *testing.T)
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,-1000,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,-1000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,+0000,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+0000,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,+1245,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,+1245,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -507,7 +508,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,CST,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,CST,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrImportFileTransactionTimezoneFormatInvalid.Message) assert.EqualError(t, err, errs.ErrImportFileTransactionTimezoneFormatInvalid.Message)
} }
@@ -532,11 +533,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,-0700,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,-0700,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
} }
@@ -561,11 +562,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,Asia/Shanghai,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,0700,E,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,0700,E,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
} }
@@ -589,7 +590,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56\tE\t1.234,56"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(123456), allNewTransactions[0].Amount) assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
@@ -615,7 +616,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56\tE\t1.234,56"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -639,7 +640,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56\tE\t1.234,56"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56\tE\t1.234,56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -670,7 +671,7 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
"2024-09-01 00:00:00,B,,123.45\n"+ "2024-09-01 00:00:00,B,,123.45\n"+
"2024-09-01 01:23:45,I,Test Category,0.12\n"+ "2024-09-01 01:23:45,I,Test Category,0.12\n"+
"2024-09-01 12:34:56,E,Test Category2,1.00\n"+ "2024-09-01 12:34:56,E,Test Category2,1.00\n"+
"2024-09-01 23:59:59,T,Test Category3,0.05"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 23:59:59,T,Test Category3,0.05"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -737,7 +738,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,T,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,Test Account,USD,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -780,12 +781,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,T,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,Test Account,CNY,1.23,Test Account2,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+ "2024-09-01 01:23:45,B,Test Account,USD,123.45,,,\n"+
"2024-09-01 12:34:56,T,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,Test Account2,CNY,1.23,Test Account,EUR,1.10"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -815,11 +816,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 01:23:45,B,Test Account,XXX,123.45,,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 01:23:45,B,Test Account,XXX,123.45,,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 01:23:45,T,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 01:23:45,T,Test Account,USD,123.45,Test Account2,XXX,123.45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -850,7 +851,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
"2024-09-01 00:00:00,B,123.45000000,\n"+ "2024-09-01 00:00:00,B,123.45000000,\n"+
"2024-09-01 01:23:45,I,0.12000000,\n"+ "2024-09-01 01:23:45,I,0.12000000,\n"+
"2024-09-01 12:34:56,E,1.00000000,\n"+ "2024-09-01 12:34:56,E,1.00000000,\n"+
"2024-09-01 23:59:59,T,0.05000000,0.35000000"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 23:59:59,T,0.05000000,0.35000000"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -895,7 +896,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// normal space // normal space
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -904,7 +905,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// no-break space (NBSP) // no-break space (NBSP)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,1 234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -913,7 +914,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// narrow no-break space (NNBSP) // narrow no-break space (NNBSP)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,1234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,1234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -922,7 +923,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// figure space // figure space
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,1234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,1234,\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -954,11 +955,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
} }
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,E,Test Account,123 45,,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,E,Test Account,123 45,,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2,123 45"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,Test Account,123.45,Test Account2,123 45"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -985,14 +986,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,E,Test Account,123.45,"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,E,Test Account,123.45,"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(12345), allNewTransactions[0].Amount) assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount) assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,T,Test Account,123.45,Test Account2"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,Test Account,123.45,Test Account2"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(12345), allNewTransactions[0].Amount) assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
@@ -1020,7 +1021,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,E,123.45,123.45;45.56"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,E,123.45,123.45;45.56"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -1049,14 +1050,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,E,123.45,,,1"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,E,123.45,,,1"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude) assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude) assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,E,123.45,a b"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,E,123.45,a b"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message) assert.EqualError(t, err, errs.ErrGeographicLocationInvalid.Message)
} }
@@ -1081,7 +1082,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
} }
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -1121,7 +1122,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
} }
_, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 00:00:00,E,123.45,foo;;bar.;#test;hello\tworld;;"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -1152,7 +1153,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 12:34:56,T,123.45,foo bar\t#test"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "2024-09-01 12:34:56,T,123.45,foo bar\t#test"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -147,7 +147,7 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location()) rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
if t.transactionDataTable.timeFormatIncludeTimezone { if t.transactionDataTable.timeFormatIncludeTimezone {
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location()) rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
} }
} }
@@ -3,6 +3,7 @@ package feidee
import ( import (
"bytes" "bytes"
"strings" "strings"
"time"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform" "golang.org/x/text/transform"
@@ -61,7 +62,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data // ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
fallback := unicode.UTF8.NewDecoder() fallback := unicode.UTF8.NewDecoder()
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback)) reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
@@ -97,7 +98,7 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppTransactionDataTable(ctx core.Context, commonDataTable datatable.CommonDataTable) (datatable.TransactionDataTable, error) { func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppTransactionDataTable(ctx core.Context, commonDataTable datatable.CommonDataTable) (datatable.TransactionDataTable, error) {
@@ -31,7 +31,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MinimumValidData(t *testi
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\"\n"+ "\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\"\n"+
"\"转出\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转出\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account2\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -122,7 +122,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"负债变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+ "\"负债变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"+
"\"负债变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"负债变更\",\"2024-09-01 01:00:00\",\"\",\"Test Account2\",\"-0.12\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -171,12 +171,12 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"收入\",\"2024-09-01T12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"收入\",\"2024-09-01T12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"收入\",\"09/01/2024 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"收入\",\"09/01/2024 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -191,7 +191,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"Type\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Type\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"0.12\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
} }
@@ -208,7 +208,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+ "\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"USD\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"USD\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -237,14 +237,14 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurren
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+ "\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+ "\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"USD\",\"123.45\",\"\",\"\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account2\",\"CNY\",\"1.23\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category3\",\"Test Account\",\"EUR\",\"1.10\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -259,19 +259,19 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"XXX\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"USD\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -286,24 +286,24 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"负债变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"负债变更\",\"2024-09-01 01:23:45\",\"\",\"Test Account\",\"123 45\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123 45\",\"\",\"00000000-0000-0000-0000-000000000001\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -319,7 +319,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testi
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"Test\n"+ "\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"Test\n"+
"A new line break\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "A new line break\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -337,7 +337,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -345,7 +345,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\",\"成员\",\"项目\",\"商家\"\n"+
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), 0, converter.DefaultImporterOptions.WithMemberAsTag().WithProjectAsTag().WithMerchantAsTag(), nil, nil, nil, nil, nil) "\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\",\"test1\",\"test2\",\"test3\""), time.UTC, converter.DefaultImporterOptions.WithMemberAsTag().WithProjectAsTag().WithMerchantAsTag(), nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -366,18 +366,18 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message) assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message) assert.EqualError(t, err, errs.ErrRelatedIdCannotBeBlank.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+ "\"转出\",\"2024-09-01 23:59:59\",\"Test Category3\",\"Test Account\",\"0.05\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
"\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"转入\",\"2024-09-02 23:59:59\",\"Test Category3\",\"Test Account\",\"0.5\",\"\",\"00000000-0000-0000-0000-000000000002\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrFoundRecordNotHasRelatedRecord.Message) assert.EqualError(t, err, errs.ErrFoundRecordNotHasRelatedRecord.Message)
} }
@@ -390,10 +390,10 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingFileHeader(t *test
DefaultCurrency: "CNY", DefaultCurrency: "CNY",
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
} }
@@ -409,36 +409,36 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *
// Missing Time Column // Missing Time Column
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"2024-09-01 00:00:00\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"2024-09-01 00:00:00\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Sub Category Column // Missing Sub Category Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 00:00:00\",\"Test Account\",\"123.45\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 00:00:00\",\"Test Account\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account Name Column // Missing Account Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"金额\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"金额\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"123.45\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"123.45\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"备注\",\"关联Id\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"备注\",\"关联Id\"\n"+
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Related ID Column // Missing Related ID Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\"\n"+ "\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\"\n"+
"\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"余额变更\",\"2024-09-01 00:00:00\",\"\",\"Test Account\",\"123.45\",\"\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -1,6 +1,8 @@
package feidee package feidee
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/converters/excel" "github.com/mayswind/ezbookkeeping/pkg/converters/excel"
@@ -34,7 +36,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data // ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data
func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, true) dataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, true)
if err != nil { if err != nil {
@@ -45,5 +47,5 @@ func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporter(feideeMymoneyElecloudTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporter(feideeMymoneyElecloudTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -25,7 +25,7 @@ func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_Minim
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx") testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 7, len(allNewTransactions)) assert.Equal(t, 7, len(allNewTransactions))
@@ -1,6 +1,8 @@
package feidee package feidee
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/converters/excel" "github.com/mayswind/ezbookkeeping/pkg/converters/excel"
@@ -33,7 +35,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the feidee mymoney (web) transaction xls data // ParseImportedData returns the imported data by parsing the feidee mymoney (web) transaction xls data
func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data, true) dataTable, err := excel.CreateNewExcelMSCFBFileBasicDataTable(data, true)
if err != nil { if err != nil {
@@ -44,5 +46,5 @@ func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx c
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(feideeMymoneyTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -25,7 +25,7 @@ func TestFeideeMymoneyTransactionDataXlsImporterParseImportedData_MinimumValidDa
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_test_file.xls") testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_test_file.xls")
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, testdata, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 7, len(allNewTransactions)) assert.Equal(t, 7, len(allNewTransactions))
@@ -2,6 +2,7 @@ package fireflyIII
import ( import (
"bytes" "bytes"
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/csv" "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
@@ -40,7 +41,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the firefly III transaction csv data // ParseImportedData returns the imported data by parsing the firefly III transaction csv data
func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true) dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
@@ -52,5 +53,5 @@ func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Co
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(fireflyIIITransactionTypeNameMapping, "", "", ",") dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(fireflyIIITransactionTypeNameMapping, "", "", ",")
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package fireflyIII
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_MinimumValidData(t *testing.
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+ "\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
"Deposit,0.12,2024-09-01T01:23:45+08:00,\"A revenue account\",\"Test Account\",\"Test Category\"\n"+ "Deposit,0.12,2024-09-01T01:23:45+08:00,\"A revenue account\",\"Test Account\",\"Test Category\"\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category2\"\n"+ "Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category2\"\n"+
"Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category3\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,0.05,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -93,11 +94,11 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidTime(t *testing.
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01T12:34:56,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01 12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -111,7 +112,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidType(t *testing.
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Type,123.45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Type,123.45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
} }
@@ -125,14 +126,14 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseAccountNameAsCategoryNa
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, "A expense account", allNewTransactions[0].OriginalCategoryName) assert.Equal(t, "A expense account", allNewTransactions[0].OriginalCategoryName)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Test Account\",\"\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Deposit,10.00,2024-09-01T12:34:56+08:00,\"A revenue account\",\"Test Account\",\"\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -149,19 +150,19 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidTimezone(t *testin
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01T12:34:56-10:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01T12:34:56+00:00,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"A expense account\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-1.00,2024-09-01T12:34:56+12:45,\"Test Account\",\"A expense account\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -178,7 +179,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidAccountCurrency(t
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+ "\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -204,7 +205,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCu
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,10.00,15.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -215,7 +216,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCu
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency) assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,10.00,2024-09-01T12:34:56+08:00,USD,EUR,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -225,7 +226,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCu
assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency) assert.Equal(t, "EUR", allNewTransactions[0].OriginalDestinationAccountCurrency)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"Transfer,10.00,2024-09-01T12:34:56+08:00,USD,,\"Test Account\",\"Test Account2\",\"Test Category\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,10.00,2024-09-01T12:34:56+08:00,USD,,\"Test Account\",\"Test Account2\",\"Test Category\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -244,12 +245,12 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAccountCurrency(
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+ "\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account\",\"Test Account2\",\"Test Category3\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account\",\"Test Account2\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+ "\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,USD,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"+
"Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account2\",\"Test Account\",\"Test Category3\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,1.23,1.10,2024-09-01T23:59:59+08:00,CNY,EUR,\"Test Account2\",\"Test Account\",\"Test Category3\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -263,11 +264,11 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseNotSupportedCurrency(t
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",123.45,,2024-09-01T00:00:00+08:00,XXX,,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,currency_code,foreign_currency_code,source_name,destination_name,category\n"+
"Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,123.45,123.45,2024-09-01T23:59:59+08:00,USD,XXX,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -281,11 +282,11 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAmount(t *testin
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name,category\n"+
"Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-123 45,2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,foreign_amount,date,source_name,destination_name,category\n"+
"Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category2\""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Transfer,123.45,123 45,2024-09-01T23:59:59+08:00,\"Test Account\",\"Test Account2\",\"Test Category2\""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -299,7 +300,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseDescription(t *testing.
} }
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,description,date,source_name,destination_name,category\n"+
"Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-123.45,\"foo bar\t#test\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -316,7 +317,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseTags(t *testing.T) {
} }
allNewTransactions, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+ allNewTransactions, _, _, _, _, allNewTags, err := importer.ParseImportedData(context, user, []byte("type,amount,tags,date,source_name,destination_name,category\n"+
"Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "Withdrawal,-123.45,\"tag1,tag2,tag3\",2024-09-01T12:34:56+08:00,\"Test Account\",\"A expense account\",\"Test Category\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -338,7 +339,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_MissingFileHeader(t *testing
DefaultCurrency: "CNY", DefaultCurrency: "CNY",
} }
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -353,31 +354,31 @@ func TestFireFlyIIICsvFileimporterParseImportedData_MissingRequiredColumn(t *tes
// Missing Time Column // Missing Time Column
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+ _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("type,amount,source_name,destination_name,category\n"+
"\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",123.45,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("amount,date,source_name,destination_name,category\n"+
"123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Sub Category Column // Missing Sub Category Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,destination_name\n"+
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\"\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\"\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account Name Column // Missing Account Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,destination_name,category\n"+
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Test Account\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,date,source_name,destination_name,category\n"+
"\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\"Test Account\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account2 Name Column // Missing Account2 Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+ _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("type,amount,date,source_name,category\n"+
"\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "\"Opening balance\",123.45,2024-09-01T00:00:00+08:00,\"Initial balance for \"\"Test Account\"\"\",\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -44,7 +44,7 @@ func (p *fireflyIIITransactionDataRowParser) Parse(data map[datatable.Transactio
} }
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location()) rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location()) rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
} }
// trim trailing zero in decimal // trim trailing zero in decimal
@@ -1,6 +1,8 @@
package gnucash package gnucash
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -24,7 +26,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the gnucash transaction data // ParseImportedData returns the imported data by parsing the gnucash transaction data
func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
gnucashDataReader, err := createNewGnuCashDatabaseReader(data) gnucashDataReader, err := createNewGnuCashDatabaseReader(data)
if err != nil { if err != nil {
@@ -45,5 +47,5 @@ func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, use
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(gnucashTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(gnucashTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -194,7 +195,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *tes
DefaultCurrency: "CNY", DefaultCurrency: "CNY",
} }
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(gnucashMinimumValidDataCase), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(gnucashMinimumValidDataCase), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags) checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -218,7 +219,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData
assert.Nil(t, err) assert.Nil(t, err)
gzippedData := buffer.Bytes() gzippedData := buffer.Bytes()
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, gzippedData, 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, gzippedData, time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags) checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -357,7 +358,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
"</gnc:book>\n"+ "</gnc:book>\n"+
"</gnc-v2>\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</gnc-v2>\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags) checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -389,7 +390,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -409,7 +410,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -439,7 +440,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -461,7 +462,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime)) assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -558,7 +559,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
"</gnc:book>\n"+ "</gnc:book>\n"+
"</gnc-v2>\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</gnc-v2>\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -600,7 +601,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -623,7 +624,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -656,7 +657,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -676,7 +677,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -696,7 +697,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -727,7 +728,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -756,7 +757,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -806,7 +807,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message) assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
} }
@@ -873,7 +874,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -901,7 +902,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message) assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
// Missing Transaction Splits Node // Missing Transaction Splits Node
@@ -912,7 +913,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+ " <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+
" </trn:date-posted>\n"+ " </trn:date-posted>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidGnuCashFile.Message) assert.EqualError(t, err, errs.ErrInvalidGnuCashFile.Message)
// Missing Transaction Split Quantity Node // Missing Transaction Split Quantity Node
@@ -931,7 +932,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
// Missing Transaction Split Account Node // Missing Transaction Split Account Node
@@ -951,7 +952,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+ " </trn:split>\n"+
" </trn:splits>\n"+ " </trn:splits>\n"+
"</gnc:transaction>\n"+ "</gnc:transaction>\n"+
gnucashCommonValidDataCaseFooter), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) gnucashCommonValidDataCaseFooter), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingAccountData.Message) assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
} }
@@ -124,7 +124,7 @@ func (t *gnucashTransactionDataRowIterator) parseTransaction(ctx core.Context, u
} }
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location()) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location()) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
if len(gnucashTransaction.Splits) == 2 { if len(gnucashTransaction.Splits) == 2 {
splitData1 := gnucashTransaction.Splits[0] splitData1 := gnucashTransaction.Splits[0]
@@ -1,6 +1,8 @@
package iif package iif
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -23,7 +25,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the intuit interchange format (iif) data // ParseImportedData returns the imported data by parsing the intuit interchange format (iif) data
func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
iifDataReader := createNewIifDataReader(data) iifDataReader := createNewIifDataReader(data)
accountDatasets, transactionDatasets, err := iifDataReader.read(ctx) accountDatasets, transactionDatasets, err := iifDataReader.read(ctx)
@@ -39,5 +41,5 @@ func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, use
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(iifTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(iifTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package iif
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -50,7 +51,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
"ENDTRNS\t\t\t\t\n"+ "ENDTRNS\t\t\t\t\n"+
"TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.56\n"+ "TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.56\n"+
"SPL\tCREDIT CARD\t09/07/2024\tTest Account2\t-34.56\n"+ "SPL\tCREDIT CARD\t09/07/2024\tTest Account2\t-34.56\n"+
"ENDTRNS\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -147,7 +148,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Category\t-123.45\n"+ "SPL\t09/01/2024\tTest Category\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -203,7 +204,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
"ENDTRNS\t\t\t\t\n"+ "ENDTRNS\t\t\t\t\n"+
"!ACCNT\tTEST\tNAME\tACCNTTYPE\n"+ "!ACCNT\tTEST\tNAME\tACCNTTYPE\n"+
"ACCNT\t\tTest Category\tINC\n"+ "ACCNT\t\tTest Category\tINC\n"+
"ACCNT\t\tTest Category2\tEXP\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ACCNT\t\tTest Category2\tEXP\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -268,7 +269,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
"ENDTRNS\t\t\t\n"+ "ENDTRNS\t\t\t\n"+
"TRNS\t09/02/2024\tTest Account2\t-123.45\n"+ "TRNS\t09/02/2024\tTest Account2\t-123.45\n"+
"SPL\t09/02/2024\tTest Parent Category2:Test Category2\t123.45\n"+ "SPL\t09/02/2024\tTest Parent Category2:Test Category2\t123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -323,7 +324,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
"ENDTRNS\t\t\t\n"+ "ENDTRNS\t\t\t\n"+
"TRNS\t2024/9/4\tTest Account\t123.45\n"+ "TRNS\t2024/9/4\tTest Account\t123.45\n"+
"SPL\t2024/9/4\tTest Account2\t-123.45\n"+ "SPL\t2024/9/4\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -355,7 +356,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
"ENDTRNS\t\t\t\n"+ "ENDTRNS\t\t\t\n"+
"TRNS\t9/3/2024\tTest Account\t123.45\n"+ "TRNS\t9/3/2024\tTest Account\t123.45\n"+
"SPL\t9/3/2024\tTest Account2\t-123.45\n"+ "SPL\t9/3/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -386,7 +387,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
"ENDTRNS\t\t\t\n"+ "ENDTRNS\t\t\t\n"+
"TRNS\t24/9/3\tTest Account\t123.45\n"+ "TRNS\t24/9/3\tTest Account\t123.45\n"+
"SPL\t24/9/3\tTest Account2\t-123.45\n"+ "SPL\t24/9/3\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -411,7 +412,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09-01-2024\tTest Account\t123.45\n"+ "TRNS\t09-01-2024\tTest Account\t123.45\n"+
"SPL\t09-01-2024\tTest Account2\t-123.45\n"+ "SPL\t09-01-2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -420,7 +421,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t2024-09-01\tTest Account\t123.45\n"+ "TRNS\t2024-09-01\tTest Account\t123.45\n"+
"SPL\t2024-09-01\tTest Account2\t-123.45\n"+ "SPL\t2024-09-01\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -429,7 +430,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t9/24\tTest Account\t123.45\n"+ "TRNS\t9/24\tTest Account\t123.45\n"+
"SPL\t9/24\tTest Account2\t-123.45\n"+ "SPL\t9/24\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -448,7 +449,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t9/01/2024\tTest Account\t123,456.78\n"+ "TRNS\t9/01/2024\tTest Account\t123,456.78\n"+
"SPL\t9/01/2024\tTest Account2\t-123,456.78\n"+ "SPL\t9/01/2024\tTest Account2\t-123,456.78\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -471,7 +472,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123 45\n"+ "TRNS\t09/01/2024\tTest Account\t123 45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -480,7 +481,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123 45\n"+ "SPL\t09/01/2024\tTest Account2\t-123 45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -499,7 +500,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"!ENDTRNS\t\t\t\t\t\n"+ "!ENDTRNS\t\t\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+ "TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+
"SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+ "SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+
"ENDTRNS\t\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -511,7 +512,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"!ENDTRNS\t\t\t\t\t\n"+ "!ENDTRNS\t\t\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\tTest\t123.45\t\n"+ "TRNS\t09/01/2024\tTest Account\tTest\t123.45\t\n"+
"SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+ "SPL\t09/01/2024\tTest Account2\t\t-123.45\t\n"+
"ENDTRNS\t\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -553,7 +554,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+ "TRNS\t09/05/2024\tTest Category2\t100.00\n"+
"SPL\t09/05/2024\tTest Account3\t-40.00\n"+ "SPL\t09/05/2024\tTest Account3\t-40.00\n"+
"SPL\t09/05/2024\tTest Account4\t-60.00\n"+ "SPL\t09/05/2024\tTest Account4\t-60.00\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -667,7 +668,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+ "TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"foo\ttest#bar\"\n"+ "SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"foo\ttest#bar\"\n"+
"SPL\t09/01/2024\tTest Account3\t\t-23.45\t\n"+ "SPL\t09/01/2024\tTest Account3\t\t-23.45\t\n"+
"ENDTRNS\t\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -682,7 +683,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"test\"\n"+ "SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"test\"\n"+
"SPL\t09/01/2024\tTest Account3\tfoo\t-12.34\t\n"+ "SPL\t09/01/2024\tTest Account3\tfoo\t-12.34\t\n"+
"SPL\t09/01/2024\tTest Account4\t\t-11.11\t\n"+ "SPL\t09/01/2024\tTest Account4\t\t-11.11\t\n"+
"ENDTRNS\t\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(allNewTransactions)) assert.Equal(t, 3, len(allNewTransactions))
@@ -708,7 +709,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\tBEGINBALCHECK\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\tBEGINBALCHECK\t09/01/2024\tTest Account\t123.45\n"+
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account2\t-100.00\n"+ "SPL\tBEGINBALCHECK\t09/01/2024\tTest Account2\t-100.00\n"+
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account3\t-23.45\n"+ "SPL\tBEGINBALCHECK\t09/01/2024\tTest Account3\t-23.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message) assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
// Transaction with invalid amount // Transaction with invalid amount
@@ -719,7 +720,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\t09/01/2024\tTest Account\t123 45\n"+ "TRNS\t09/01/2024\tTest Account\t123 45\n"+
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+ "SPL\t09/01/2024\tTest Account2\t-100.00\n"+
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+ "SPL\t09/01/2024\tTest Account3\t-23.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
// Transaction split data with invalid amount // Transaction split data with invalid amount
@@ -730,7 +731,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-100 00\n"+ "SPL\t09/01/2024\tTest Account2\t-100 00\n"+
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+ "SPL\t09/01/2024\tTest Account3\t-23.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
// Transaction amount not equal to sum of split data amount // Transaction amount not equal to sum of split data amount
@@ -741,7 +742,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\t09/01/2024\tTest Account\t123.00\n"+ "TRNS\t09/01/2024\tTest Account\t123.00\n"+
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+ "SPL\t09/01/2024\tTest Account2\t-100.00\n"+
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+ "SPL\t09/01/2024\tTest Account3\t-23.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message) assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
} }
@@ -760,7 +761,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction And Split Line // Missing Transaction And Split Line
@@ -768,7 +769,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+ "!TRNS\tDATE\tACCNT\tAMOUNT\n"+
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Split Line // Missing Split Line
@@ -777,7 +778,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction End Line // Missing Transaction End Line
@@ -786,7 +787,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "SPL\t09/01/2024\tTest Account2\t-123.45\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction End Line (following is another header) // Missing Transaction End Line (following is another header)
@@ -797,7 +798,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"!ACCNT\tNAME\tACCNTTYPE\n"+ "!ACCNT\tNAME\tACCNTTYPE\n"+
"ACCNT\tTest Account\tBANK\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ACCNT\tTest Account\tBANK\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Invalid Line // Invalid Line
@@ -808,7 +809,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"TEST\t\t\t\n"+ "TEST\t\t\t\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Repeat Transaction Line // Repeat Transaction Line
@@ -819,7 +820,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Repeat Transaction End Line // Repeat Transaction End Line
@@ -831,7 +832,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"+ "ENDTRNS\t\t\t\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
} }
@@ -848,25 +849,25 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction Sample Line // Missing Transaction Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Split Sample Line // Missing Split Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+ "!TRNS\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction End Sample Line // Missing Transaction End Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+ "!TRNS\tDATE\tACCNT\tAMOUNT\n"+
"!SPL\tDATE\tACCNT\tAMOUNT\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "!SPL\tDATE\tACCNT\tAMOUNT\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Missing Transaction End Sample Line (following is data line) // Missing Transaction End Sample Line (following is data line)
@@ -875,14 +876,14 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
"!SPL\tDATE\tACCNT\tAMOUNT\n"+ "!SPL\tDATE\tACCNT\tAMOUNT\n"+
"TRNS\t09/01/2024\tTest Account\t123.45\n"+ "TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\n"+ "SPL\t09/01/2024\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
// Invalid Sample Line // Invalid Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+ "!TRNS\tDATE\tACCNT\tAMOUNT\n"+
"!TEST\tDATE\tACCNT\tAMOUNT\n"+ "!TEST\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "!ENDTRNS\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message) assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
} }
@@ -902,7 +903,7 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\tTest Account\t123.45\n"+ "TRNS\tTest Account\t123.45\n"+
"SPL\tTest Account2\t-123.45\n"+ "SPL\tTest Account2\t-123.45\n"+
"ENDTRNS\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Account Column // Missing Account Column
@@ -912,7 +913,7 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\t123.45\n"+ "TRNS\t09/01/2024\t123.45\n"+
"SPL\t09/01/2024\t-123.45\n"+ "SPL\t09/01/2024\t-123.45\n"+
"ENDTRNS\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
@@ -922,6 +923,6 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+ "!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\n"+ "TRNS\t09/01/2024\tTest Account\n"+
"SPL\t09/01/2024\tTest Account2\n"+ "SPL\t09/01/2024\tTest Account2\n"+
"ENDTRNS\t\t\t\t\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "ENDTRNS\t\t\t\t\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -2,6 +2,7 @@ package jdcom
import ( import (
"bytes" "bytes"
"time"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform" "golang.org/x/text/transform"
@@ -27,7 +28,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the jd.com finance transaction csv data // ParseImportedData returns the imported data by parsing the jd.com finance transaction csv data
func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
fallback := unicode.UTF8.NewDecoder() fallback := unicode.UTF8.NewDecoder()
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback)) reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
@@ -60,5 +61,5 @@ func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, jdComFinanceTransactionSupportedColumns, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, jdComFinanceTransactionSupportedColumns, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(jdComFinanceTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(jdComFinanceTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -31,7 +31,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testin
"2025-09-01 12:34:56,xxx,xxx,123.45,银行卡,交易成功,支出,其他网购\n" + "2025-09-01 12:34:56,xxx,xxx,123.45,银行卡,交易成功,支出,其他网购\n" +
"2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" + "2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" +
"2025-09-02 23:59:59,xxx,京东余额提现,0.03,银行卡,交易成功,不计收支,余额\n" "2025-09-02 23:59:59,xxx,京东余额提现,0.03,银行卡,交易成功,不计收支,余额\n"
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, len(allNewTransactions)) assert.Equal(t, 4, len(allNewTransactions))
@@ -111,7 +111,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *
"2025-09-01 02:34:56,xxx,xxx,0.12(已全额退款),银行卡,交易成功,不计收支\n" + "2025-09-01 02:34:56,xxx,xxx,0.12(已全额退款),银行卡,交易成功,不计收支\n" +
"2025-09-02 01:23:45,xxx,xxx,3.45,银行卡,退款成功,不计收支\n" + "2025-09-02 01:23:45,xxx,xxx,3.45,银行卡,退款成功,不计收支\n" +
"2025-09-02 02:34:56,xxx,xxx,123.45(已退款3.45),银行卡,交易成功,支出\n" "2025-09-02 02:34:56,xxx,xxx,123.45(已退款3.45),银行卡,交易成功,支出\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid) assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -154,7 +154,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01T01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n" "2025-09-01T01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
data2 := "导出信息:\n" + data2 := "导出信息:\n" +
@@ -163,7 +163,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"09/01/2025 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n" "09/01/2025 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -182,7 +182,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,转账\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,转账\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -201,7 +201,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *test
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,¥0.12,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,xxx,¥0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -221,7 +221,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
"2025-09-01 01:23:45,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" "2025-09-01 01:23:45,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -236,7 +236,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
"2025-09-01 01:23:45,xxx,京东余额提现,0.05,银行卡,交易成功,不计收支,余额\n" "2025-09-01 01:23:45,xxx,京东余额提现,0.05,银行卡,交易成功,不计收支,余额\n"
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -253,7 +253,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n" "2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -270,7 +270,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n" "2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -287,7 +287,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n" "2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -303,7 +303,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n" "2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -327,7 +327,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,,0.12,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,,0.12,银行卡,交易成功,支出\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -339,7 +339,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" + "交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\"foo\"\"bar,\ntest\"\n" "2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\"foo\"\"bar,\ntest\"\n"
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment) assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
@@ -349,7 +349,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" + "\n" +
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" + "交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\n" "2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\n"
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, "Test", allNewTransactions[0].Comment) assert.Equal(t, "Test", allNewTransactions[0].Comment)
} }
@@ -369,7 +369,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransacti
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,xxxx,支出\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,xxxx,支出\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -388,7 +388,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTra
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,不计收支\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,不计收支\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -403,10 +403,10 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testi
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
} }
@@ -426,7 +426,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" + "商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"xxx,xxx,0.12,银行卡,交易成功,支出\n" "xxx,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
// Missing Merchant Name Column // Missing Merchant Name Column
@@ -436,7 +436,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" + "交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Transaction Memo Column // Missing Transaction Memo Column
@@ -446,7 +446,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
@@ -456,7 +456,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" + "交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,银行卡,交易成功,支出\n" "2025-09-01 01:23:45,xxx,xxx,银行卡,交易成功,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Related Account Column // Missing Related Account Column
@@ -466,7 +466,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,交易状态,收/支\n" + "交易时间,商户名称,交易说明,金额,交易状态,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,交易成功,支出\n" "2025-09-01 01:23:45,xxx,xxx,0.12,交易成功,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Status Column // Missing Status Column
@@ -476,7 +476,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,支出\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,支出\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data6), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
@@ -486,7 +486,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" + "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" +
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功\n" "2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data7), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -504,6 +504,6 @@ func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testi
"日期区间:2025-01-01 至 2025-09-01\n" + "日期区间:2025-01-01 至 2025-09-01\n" +
"\n" + "\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -1,6 +1,8 @@
package mt package mt
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -22,7 +24,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the mt940 file statement data // ParseImportedData returns the imported data by parsing the mt940 file statement data
func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
mt940DataReader := createNewMT940FileReader(data) mt940DataReader := createNewMT940FileReader(data)
mt940Data, err := mt940DataReader.read(ctx) mt940Data, err := mt940DataReader.read(ctx)
@@ -38,5 +40,5 @@ func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, u
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(mt940TransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(mt940TransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package mt
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -32,7 +33,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
:61:2506020603D234,56NTRFFOOBAR :61:2506020603D234,56NTRFFOOBAR
:86:Transaction 2 :86:Transaction 2
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -92,7 +93,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
:61:250603C1,NTRFTEST :61:250603C1,NTRFTEST
:86:Transaction 3 :86:Transaction 3
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(allNewTransactions)) assert.Equal(t, 3, len(allNewTransactions))
@@ -118,7 +119,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
:60F:C250601CNY123,45 :60F:C250601CNY123,45
:61:2506010602C123 45NTRFTEST :61:2506010602C123 45NTRFTEST
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message) assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -129,7 +130,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
:60F:C250601CNY123,45 :60F:C250601CNY123,45
:61:2506010602C12.45NTRFTEST :61:2506010602C12.45NTRFTEST
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidMT940File.Message) assert.EqualError(t, err, errs.ErrInvalidMT940File.Message)
} }
@@ -157,7 +158,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
:61:250604RD123,45NTRFTEST :61:250604RD123,45NTRFTEST
:86:Transaction 4 :86:Transaction 4
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, len(allNewTransactions)) assert.Equal(t, 4, len(allNewTransactions))
@@ -187,7 +188,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
Part 2 Part 2
Part 3 Part 3
:62F:C250601CNY123,45 :62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -210,6 +211,6 @@ func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testi
:28C:123/1 :28C:123/1
:61:250601C123,45NTRFTEST :61:250601C123,45NTRFTEST
:86:Transaction 1 :86:Transaction 1
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) -}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -1,6 +1,8 @@
package ofx package ofx
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -23,7 +25,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data // 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, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
ofxDataReader, err := createNewOFXFileReader(ctx, data) ofxDataReader, err := createNewOFXFileReader(ctx, data)
if err != nil { if err != nil {
@@ -44,5 +46,5 @@ func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(ofxTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(ofxTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package ofx
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -77,7 +78,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
" </CCSTMTRS>\n"+ " </CCSTMTRS>\n"+
" </CCSTMTTRNRS>\n"+ " </CCSTMTTRNRS>\n"+
" </CREDITCARDMSGSRSV1>\n"+ " </CREDITCARDMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -211,7 +212,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
" </CCSTMTRS>\n"+ " </CCSTMTRS>\n"+
" </CCSTMTTRNRS>\n"+ " </CCSTMTTRNRS>\n"+
" </CREDITCARDMSGSRSV1>\n"+ " </CREDITCARDMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -296,7 +297,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -338,7 +339,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -360,7 +361,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -382,7 +383,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -404,7 +405,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -436,7 +437,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -472,7 +473,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -505,7 +506,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -542,7 +543,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -568,7 +569,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -596,7 +597,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -629,7 +630,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingAccountData.Message) assert.EqualError(t, err, errs.ErrMissingAccountData.Message)
} }
@@ -661,7 +662,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message)
} }
@@ -693,7 +694,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message) assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
// Missing Transaction Type Node // Missing Transaction Type Node
@@ -715,7 +716,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message)
// Missing Amount Node // Missing Amount Node
@@ -737,6 +738,6 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+ " </STMTRS>\n"+
" </STMTTRNRS>\n"+ " </STMTTRNRS>\n"+
" </BANKMSGSRSV1>\n"+ " </BANKMSGSRSV1>\n"+
"</OFX>"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "</OFX>"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -1,6 +1,8 @@
package qif package qif
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
@@ -35,7 +37,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the quicken interchange format (qif) transaction data // ParseImportedData returns the imported data by parsing the quicken interchange format (qif) transaction data
func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
qifDataReader := createNewQifDataReader(data) qifDataReader := createNewQifDataReader(data)
qifData, err := qifDataReader.read(ctx) qifData, err := qifDataReader.read(ctx)
@@ -51,5 +53,5 @@ func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(qifTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(qifTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -2,6 +2,7 @@ package qif
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -43,7 +44,7 @@ func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
"D2024-09-05\n"+ "D2024-09-05\n"+
"T0.06\n"+ "T0.06\n"+
"L[Test Account2]\n"+ "L[Test Account2]\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -138,7 +139,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime
"^\n"+ "^\n"+
"D2024'9.5\n"+ "D2024'9.5\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -176,7 +177,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime
"^\n"+ "^\n"+
"D9.5'2024\n"+ "D9.5'2024\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -214,7 +215,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime
"^\n"+ "^\n"+
"D5'9.2024\n"+ "D5'9.2024\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -252,7 +253,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateForma
"^\n"+ "^\n"+
"D24'9.5\n"+ "D24'9.5\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -278,7 +279,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024 09 01\n"+ "D2024 09 01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -295,7 +296,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123,456.78\n"+ "T-123,456.78\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
@@ -317,7 +318,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123 45\n"+ "T-123 45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -334,7 +335,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Cash\n"+ "!Type:Cash\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -347,7 +348,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:CCard\n"+ "!Type:CCard\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -360,7 +361,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Oth A\n"+ "!Type:Oth A\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -373,7 +374,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Oth L\n"+ "!Type:Oth L\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -399,7 +400,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccount(t *testing.T) {
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -425,7 +426,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithLeadingPlusSign(
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"T+123.45\n"+ "T+123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -446,7 +447,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseSubCategory(t *testing.T)
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"LTest Category:Sub Category\n"+ "LTest Category:Sub Category\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -477,7 +478,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"D2024-09-02\n"+ "D2024-09-02\n"+
"T-234.56\n"+ "T-234.56\n"+
"PTest2\n"+ "PTest2\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -494,7 +495,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"D2024-09-02\n"+ "D2024-09-02\n"+
"T-234.56\n"+ "T-234.56\n"+
"PTest2\n"+ "PTest2\n"+
"^\n"), 0, converter.DefaultImporterOptions.WithPayeeAsDescription(), nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions.WithPayeeAsDescription(), nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(allNewTransactions)) assert.Equal(t, 2, len(allNewTransactions))
@@ -516,7 +517,7 @@ func TestQIFTransactionDataFileParseImportedData_WithAdditionalOptions(t *testin
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"PTest2\n"+ "PTest2\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -527,7 +528,7 @@ func TestQIFTransactionDataFileParseImportedData_WithAdditionalOptions(t *testin
"D2024-09-01\n"+ "D2024-09-01\n"+
"T-123.45\n"+ "T-123.45\n"+
"PTest2\n"+ "PTest2\n"+
"^\n"), 0, converter.DefaultImporterOptions.WithPayeeAsTag(), nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions.WithPayeeAsTag(), nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -548,13 +549,13 @@ func TestQIFTransactionDataFileParseImportedData_MissingRequiredFields(t *testin
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"!Type:Bank\n"+ "!Type:Bank\n"+
"T-123.45\n"+ "T-123.45\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message) assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message)
// Missing Amount Field // Missing Amount Field
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!Type:Bank\n"+ "!Type:Bank\n"+
"D2024-09-01\n"+ "D2024-09-01\n"+
"^\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) "^\n"), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -2,6 +2,7 @@ package wechat
import ( import (
"bytes" "bytes"
"time"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform" "golang.org/x/text/transform"
@@ -27,7 +28,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the wechat pay transaction csv data // ParseImportedData returns the imported data by parsing the wechat pay transaction csv data
func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
fallback := unicode.UTF8.NewDecoder() fallback := unicode.UTF8.NewDecoder()
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback)) reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
@@ -58,5 +59,5 @@ func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Con
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
@@ -32,7 +32,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T
"2024-09-01 12:34:56,商户消费,支出,¥123.45,支付成功\n" + "2024-09-01 12:34:56,商户消费,支出,¥123.45,支付成功\n" +
"2024-09-01 23:59:59,零钱充值,/,¥0.05,充值完成\n" + "2024-09-01 23:59:59,零钱充值,/,¥0.05,充值完成\n" +
"2024-09-02 23:59:59,零钱提现,/,¥0.03,提现已到账\n" "2024-09-02 23:59:59,零钱提现,/,¥0.03,提现已到账\n"
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, len(allNewTransactions)) assert.Equal(t, 4, len(allNewTransactions))
@@ -109,7 +109,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseRefundTransaction(t *tes
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01 01:23:45,xxx-退款,收入,¥0.12,已全额退款\n" "2024-09-01 01:23:45,xxx-退款,收入,¥0.12,已全额退款\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid) assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -136,7 +136,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01T01:23:45,二维码收款,收入,¥0.12,已收钱\n" "2024-09-01T01:23:45,二维码收款,收入,¥0.12,已收钱\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
data2 := "微信支付账单明细,,,,\n" + data2 := "微信支付账单明细,,,,\n" +
@@ -146,7 +146,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"09/01/2024 12:34:56,二维码收款,收入,¥0.12,已收钱\n" "09/01/2024 12:34:56,二维码收款,收入,¥0.12,已收钱\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
} }
@@ -166,7 +166,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01T01:23:45,xxx,,¥0.12,支付成功\n" "2024-09-01T01:23:45,xxx,,¥0.12,支付成功\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -186,7 +186,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01 01:23:45,二维码收款,收入,¥,已收钱\n" "2024-09-01 01:23:45,二维码收款,收入,¥,已收钱\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrAmountInvalid.Message) assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
} }
@@ -207,7 +207,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),支付方式,当前状态\n" + "交易时间,交易类型,收/支,金额(元),支付方式,当前状态\n" +
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,/,已收钱\n" "2024-09-01 01:23:45,二维码收款,收入,¥0.12,/,已收钱\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -223,7 +223,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"2024-09-01 01:23:45,xxx-退款,收入,¥0.12,test,已全额退款\n" "2024-09-01 01:23:45,xxx-退款,收入,¥0.12,test,已全额退款\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -239,7 +239,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"2024-09-01 23:59:59,零钱充值,/,¥0.05,test,充值完成\n" "2024-09-01 23:59:59,零钱充值,/,¥0.05,test,充值完成\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -256,7 +256,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"2024-09-02 23:59:59,零钱提现,/,¥0.03,test,提现已到账\n" "2024-09-02 23:59:59,零钱提现,/,¥0.03,test,提现已到账\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -273,7 +273,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"2024-09-03 23:59:59,信用卡还款,/,¥0.01,零钱,支付成功\n" "2024-09-03 23:59:59,信用卡还款,/,¥0.01,零钱,支付成功\n"
assert.Nil(t, err) assert.Nil(t, err)
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -297,7 +297,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态,备注\n" + "交易时间,交易类型,收/支,金额(元),当前状态,备注\n" +
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱,\"/\"\n" "2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱,\"/\"\n"
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
@@ -310,7 +310,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" + "交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" +
"2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"foo\"\"bar,\ntest\"\n" "2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"foo\"\"bar,\ntest\"\n"
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment) assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
@@ -321,7 +321,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" + "交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\n" +
"2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"\"\n" "2024-09-01 01:23:45,二维码收款,Test,收入,¥0.12,已收钱,\"\"\n"
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, "Test", allNewTransactions[0].Comment) assert.Equal(t, "Test", allNewTransactions[0].Comment)
} }
@@ -342,7 +342,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_SkipUnknownTransferTransactio
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" + "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01 23:59:59,/,/,¥0.05,充值完成\n" "2024-09-01 23:59:59,/,/,¥0.05,充值完成\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -357,10 +357,10 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.
data := "交易时间,交易类型,收/支,金额(元),当前状态\n" + data := "交易时间,交易类型,收/支,金额(元),当前状态\n" +
"2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱\n" "2024-09-01 01:23:45,二维码收款,收入,¥0.12,已收钱\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(""), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message) assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
} }
@@ -381,7 +381,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易类型,收/支,金额(元),当前状态\n" + "交易类型,收/支,金额(元),当前状态\n" +
"二维码收款,收入,¥0.12,已收钱\n" "二维码收款,收入,¥0.12,已收钱\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data1), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Category Column // Missing Category Column
@@ -392,7 +392,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,收/支,金额(元),当前状态\n" + "交易时间,收/支,金额(元),当前状态\n" +
"2024-09-01 01:23:45,收入,¥0.12,已收钱\n" "2024-09-01 01:23:45,收入,¥0.12,已收钱\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data2), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Type Column // Missing Type Column
@@ -403,7 +403,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,金额(元),当前状态\n" + "交易时间,交易类型,金额(元),当前状态\n" +
"2024-09-01 01:23:45,二维码收款,¥0.12,已收钱\n" "2024-09-01 01:23:45,二维码收款,¥0.12,已收钱\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data3), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Amount Column // Missing Amount Column
@@ -414,7 +414,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,当前状态\n" + "交易时间,交易类型,收/支,当前状态\n" +
"2024-09-01 01:23:45,二维码收款,收入,已收钱\n" "2024-09-01 01:23:45,二维码收款,收入,已收钱\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data4), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
// Missing Status Column // Missing Status Column
@@ -425,7 +425,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元)\n" + "交易时间,交易类型,收/支,金额(元)\n" +
"2024-09-01 01:23:45,二维码收款,收入,¥0.12\n" "2024-09-01 01:23:45,二维码收款,收入,¥0.12\n"
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(data5), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
} }
@@ -444,6 +444,6 @@ func TestWeChatPayCsvFileImporterParseImportedData_NoTransactionData(t *testing.
",,,,\n" + ",,,,\n" +
"----------------------微信支付账单明细列表--------------------,,,,\n" + "----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\n" "交易时间,交易类型,收/支,金额(元),当前状态\n"
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) _, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(data), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message) assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
} }
@@ -1,6 +1,8 @@
package wechat package wechat
import ( import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter" "github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/converters/excel" "github.com/mayswind/ezbookkeeping/pkg/converters/excel"
@@ -21,7 +23,7 @@ var (
) )
// ParseImportedData returns the imported data by parsing the wechat pay transaction csv data // ParseImportedData returns the imported data by parsing the wechat pay transaction csv data
func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions converter.TransactionDataImporterOptions, 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) { func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezone *time.Location, additionalOptions converter.TransactionDataImporterOptions, 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) {
xlsxDataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, false) xlsxDataTable, err := excel.CreateNewExcelOOXMLFileBasicDataTable(data, false)
if err != nil { if err != nil {
@@ -49,5 +51,5 @@ func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Co
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser) transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping) dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(wechatPayTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezone, additionalOptions, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
} }
+1 -1
View File
@@ -266,7 +266,7 @@ func (h *mcpAddTransactionToolHandler) createNewTransactionModel(uid int64, addT
Type: transactionDbType, Type: transactionDbType,
CategoryId: categoryId, CategoryId: categoryId,
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()), TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Location()), TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), transactionTime.Location()),
AccountId: sourceAccountId, AccountId: sourceAccountId,
Amount: amount, Amount: amount,
HideAmount: false, HideAmount: false,
+9 -4
View File
@@ -219,7 +219,7 @@ func (s *AccountService) GetMaxSubAccountDisplayOrder(c core.Context, uid int64,
} }
// CreateAccounts saves a new account model to database // CreateAccounts saves a new account model to database
func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Account, mainAccountBalanceTime int64, childrenAccounts []*models.Account, childrenAccountBalanceTimes []int64, utcOffset int16) error { func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Account, mainAccountBalanceTime int64, childrenAccounts []*models.Account, childrenAccountBalanceTimes []int64, clientTimezone *time.Location) error {
if mainAccount.Uid <= 0 { if mainAccount.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -266,11 +266,14 @@ func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Acco
} }
transactionTime := defaultTransactionTime transactionTime := defaultTransactionTime
transactionUtcOffset := utils.GetTimezoneOffsetMinutes(now, clientTimezone)
if i == 0 && mainAccountBalanceTime > 0 { if i == 0 && mainAccountBalanceTime > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(mainAccountBalanceTime) transactionTime = utils.GetMinTransactionTimeFromUnixTime(mainAccountBalanceTime)
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(mainAccountBalanceTime, clientTimezone)
} else if i > 0 && len(childrenAccountBalanceTimes) > i-1 && childrenAccountBalanceTimes[i-1] > 0 { } else if i > 0 && len(childrenAccountBalanceTimes) > i-1 && childrenAccountBalanceTimes[i-1] > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(childrenAccountBalanceTimes[i-1]) transactionTime = utils.GetMinTransactionTimeFromUnixTime(childrenAccountBalanceTimes[i-1])
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(childrenAccountBalanceTimes[i-1], clientTimezone)
} else { } else {
defaultTransactionTime++ defaultTransactionTime++
} }
@@ -281,7 +284,7 @@ func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Acco
Deleted: false, Deleted: false,
Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
TransactionTime: transactionTime, TransactionTime: transactionTime,
TimezoneUtcOffset: utcOffset, TimezoneUtcOffset: transactionUtcOffset,
AccountId: allAccounts[i].AccountId, AccountId: allAccounts[i].AccountId,
Amount: allAccounts[i].Balance, Amount: allAccounts[i].Balance,
RelatedAccountId: allAccounts[i].AccountId, RelatedAccountId: allAccounts[i].AccountId,
@@ -366,7 +369,7 @@ func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Acco
} }
// ModifyAccounts saves an existed account model to database // ModifyAccounts saves an existed account model to database
func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Account, updateAccounts []*models.Account, addSubAccounts []*models.Account, addSubAccountBalanceTimes []int64, removeSubAccountIds []int64, utcOffset int16) error { func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Account, updateAccounts []*models.Account, addSubAccounts []*models.Account, addSubAccountBalanceTimes []int64, removeSubAccountIds []int64, clientTimezone *time.Location) error {
if mainAccount.Uid <= 0 { if mainAccount.Uid <= 0 {
return errs.ErrUserIdInvalid return errs.ErrUserIdInvalid
} }
@@ -407,9 +410,11 @@ func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Acco
} }
transactionTime := defaultTransactionTime transactionTime := defaultTransactionTime
transactionUtcOffset := utils.GetTimezoneOffsetMinutes(now, clientTimezone)
if len(addSubAccountBalanceTimes) > i && addSubAccountBalanceTimes[i] > 0 { if len(addSubAccountBalanceTimes) > i && addSubAccountBalanceTimes[i] > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(addSubAccountBalanceTimes[i]) transactionTime = utils.GetMinTransactionTimeFromUnixTime(addSubAccountBalanceTimes[i])
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(addSubAccountBalanceTimes[i], clientTimezone)
} else { } else {
defaultTransactionTime++ defaultTransactionTime++
} }
@@ -420,7 +425,7 @@ func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Acco
Deleted: false, Deleted: false,
Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
TransactionTime: transactionTime, TransactionTime: transactionTime,
TimezoneUtcOffset: utcOffset, TimezoneUtcOffset: transactionUtcOffset,
AccountId: childAccount.AccountId, AccountId: childAccount.AccountId,
Amount: childAccount.Balance, Amount: childAccount.Balance,
RelatedAccountId: childAccount.AccountId, RelatedAccountId: childAccount.AccountId,
+15 -10
View File
@@ -224,12 +224,17 @@ func ParseFromLongDateTimeToMaxUnixTime(t string) (time.Time, error) {
return time.ParseInLocation(longDateTimeFormat, t, timezone) return time.ParseInLocation(longDateTimeFormat, t, timezone)
} }
// ParseFromLongDateTime parses a formatted string in long date time format // ParseFromLongDateTimeInFixedUtcOffset parses a formatted string in long date time format
func ParseFromLongDateTime(t string, utcOffset int16) (time.Time, error) { func ParseFromLongDateTimeInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60) timezone := time.FixedZone("Timezone", int(utcOffset)*60)
return time.ParseInLocation(longDateTimeFormat, t, timezone) return time.ParseInLocation(longDateTimeFormat, t, timezone)
} }
// ParseFromLongDateTimeInTimeZone parses a formatted string in long date time format
func ParseFromLongDateTimeInTimeZone(t string, timezone *time.Location) (time.Time, error) {
return time.ParseInLocation(longDateTimeFormat, t, timezone)
}
// ParseFromLongDateTimeWithTimezone parses a formatted string in long date time format // ParseFromLongDateTimeWithTimezone parses a formatted string in long date time format
func ParseFromLongDateTimeWithTimezone(t string) (time.Time, error) { func ParseFromLongDateTimeWithTimezone(t string) (time.Time, error) {
return time.Parse(longDateTimeWithTimezoneFormat, t) return time.Parse(longDateTimeWithTimezoneFormat, t)
@@ -245,14 +250,14 @@ func ParseFromLongDateTimeWithTimezoneRFC3339Format(t string) (time.Time, error)
return time.Parse(longDateTimeWithTimezoneRFC3339Format, t) return time.Parse(longDateTimeWithTimezoneRFC3339Format, t)
} }
// ParseFromLongDateTimeWithoutSecond parses a formatted string in long date time format (no second) // ParseFromLongDateTimeWithoutSecondInFixedUtcOffset parses a formatted string in long date time format (no second) with fixed UTC offset
func ParseFromLongDateTimeWithoutSecond(t string, utcOffset int16) (time.Time, error) { func ParseFromLongDateTimeWithoutSecondInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60) timezone := time.FixedZone("Timezone", int(utcOffset)*60)
return time.ParseInLocation(longDateTimeWithoutSecondFormat, t, timezone) return time.ParseInLocation(longDateTimeWithoutSecondFormat, t, timezone)
} }
// ParseFromShortDateTime parses a formatted string in short date time format // ParseFromShortDateTimeInFixedUtcOffset parses a formatted string in short date time format with fixed UTC offset
func ParseFromShortDateTime(t string, utcOffset int16) (time.Time, error) { func ParseFromShortDateTimeInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60) timezone := time.FixedZone("Timezone", int(utcOffset)*60)
return time.ParseInLocation(shortDateTimeFormat, t, timezone) return time.ParseInLocation(shortDateTimeFormat, t, timezone)
} }
@@ -282,8 +287,8 @@ func IsUnixTimeEqualsYearAndMonth(unixTime int64, timezone *time.Location, year
} }
// GetTimezoneOffsetMinutes returns offset minutes according specified timezone // GetTimezoneOffsetMinutes returns offset minutes according specified timezone
func GetTimezoneOffsetMinutes(timezone *time.Location) int16 { func GetTimezoneOffsetMinutes(unixTime int64, timezone *time.Location) int16 {
_, tzOffset := time.Now().In(timezone).Zone() _, tzOffset := parseFromUnixTime(unixTime).In(timezone).Zone()
tzMinuteOffset := int16(tzOffset / 60) tzMinuteOffset := int16(tzOffset / 60)
return tzMinuteOffset return tzMinuteOffset
@@ -298,8 +303,8 @@ func GetServerTimezoneOffsetMinutes() int16 {
} }
// FormatTimezoneOffset returns "+/-HH:MM" format of timezone // FormatTimezoneOffset returns "+/-HH:MM" format of timezone
func FormatTimezoneOffset(timezone *time.Location) string { func FormatTimezoneOffset(unixTime int64, timezone *time.Location) string {
tzMinutesOffset := GetTimezoneOffsetMinutes(timezone) tzMinutesOffset := GetTimezoneOffsetMinutes(unixTime, timezone)
sign := "+" sign := "+"
hourAbsOffset := tzMinutesOffset / 60 hourAbsOffset := tzMinutesOffset / 60
+68 -17
View File
@@ -215,15 +215,36 @@ func TestParseFromLongDateTimeToMaxUnixTime(t *testing.T) {
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestParseFromLongDateTime(t *testing.T) { func TestParseFromLongDateTimeInFixedUtcOffset(t *testing.T) {
expectedValue := int64(1617228083) expectedValue := int64(1617228083)
actualTime, err := ParseFromLongDateTime("2021-04-01 06:01:23", 480) actualTime, err := ParseFromLongDateTimeInFixedUtcOffset("2021-04-01 06:01:23", 480)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
actualValue := actualTime.Unix() actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestParseFromLongDateTimeInTimeZone(t *testing.T) {
londonLocation, err := time.LoadLocation("Europe/London")
assert.Equal(t, nil, err)
// during standard time (UTC+0)
expectedValue := int64(1577858483)
actualTime, err := ParseFromLongDateTimeInTimeZone("2020-01-01 06:01:23", londonLocation)
assert.Equal(t, nil, err)
actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue)
// during daylight saving time (UTC+1)
expectedValue = int64(1619845283)
actualTime, err = ParseFromLongDateTimeInTimeZone("2021-05-01 06:01:23", londonLocation)
assert.Equal(t, nil, err)
actualValue = actualTime.Unix()
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromLongDateTimeWithTimezone(t *testing.T) { func TestParseFromLongDateTimeWithTimezone(t *testing.T) {
expectedValue := int64(1617238883) expectedValue := int64(1617238883)
actualTime, err := ParseFromLongDateTimeWithTimezone("2021-04-01 06:01:23+05:00") actualTime, err := ParseFromLongDateTimeWithTimezone("2021-04-01 06:01:23+05:00")
@@ -251,18 +272,18 @@ func TestParseFromLongDateTimeWithTimezoneRFC3339Format(t *testing.T) {
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestParseFromLongDateTimeWithoutSecond(t *testing.T) { func TestParseFromLongDateTimeWithoutSecondInFixedUtcOffset(t *testing.T) {
expectedValue := int64(1691947440) expectedValue := int64(1691947440)
actualTime, err := ParseFromLongDateTimeWithoutSecond("2023-08-13 17:24", 0) actualTime, err := ParseFromLongDateTimeWithoutSecondInFixedUtcOffset("2023-08-13 17:24", 0)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
actualValue := actualTime.Unix() actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestParseFromShortDateTime(t *testing.T) { func TestParseFromShortDateTimeInFixedUtcOffset(t *testing.T) {
expectedValue := int64(1617228083) expectedValue := int64(1617228083)
actualTime, err := ParseFromShortDateTime("2021-4-1 6:1:23", 480) actualTime, err := ParseFromShortDateTimeInFixedUtcOffset("2021-4-1 6:1:23", 480)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
actualValue := actualTime.Unix() actualValue := actualTime.Unix()
@@ -312,52 +333,82 @@ func TestIsUnixTimeEqualsYearAndMonth(t *testing.T) {
assert.Equal(t, false, actualValue) assert.Equal(t, false, actualValue)
} }
func TestGetTimezoneOffsetMinutes(t *testing.T) { func TestGetTimezoneOffsetMinutes_FixedTimezone(t *testing.T) {
timezone := time.FixedZone("Test Timezone", 120*60) timezone := time.FixedZone("Test Timezone", 120*60)
expectedValue := int16(120) expectedValue := int16(120)
actualValue := GetTimezoneOffsetMinutes(timezone) actualValue := GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 345*60) timezone = time.FixedZone("Test Timezone", 345*60)
expectedValue = int16(345) expectedValue = int16(345)
actualValue = GetTimezoneOffsetMinutes(timezone) actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -720*60) timezone = time.FixedZone("Test Timezone", -720*60)
expectedValue = int16(-720) expectedValue = int16(-720)
actualValue = GetTimezoneOffsetMinutes(timezone) actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 0) timezone = time.FixedZone("Test Timezone", 0)
expectedValue = int16(0) expectedValue = int16(0)
actualValue = GetTimezoneOffsetMinutes(timezone) actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestFormatTimezoneOffset(t *testing.T) { func TestGetTimezoneOffsetMinutes_TimezoneWithDST(t *testing.T) {
londonLocation, err := time.LoadLocation("Europe/London")
assert.Equal(t, nil, err)
// during standard time (UTC+0)
expectedValue := int16(0)
actualValue := GetTimezoneOffsetMinutes(1577858483, londonLocation) // 2020-01-01 06:01:23 +00:00
assert.Equal(t, expectedValue, actualValue)
// during daylight saving time (UTC+1)
expectedValue = int16(60)
actualValue = GetTimezoneOffsetMinutes(1619845283, londonLocation) // 2021-05-01 06:01:23 +01:00
assert.Equal(t, expectedValue, actualValue)
}
func TestFormatTimezoneOffset_FixedTimezone(t *testing.T) {
timezone := time.FixedZone("Test Timezone", 120*60) timezone := time.FixedZone("Test Timezone", 120*60)
expectedValue := "+02:00" expectedValue := "+02:00"
actualValue := FormatTimezoneOffset(timezone) actualValue := FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 345*60) timezone = time.FixedZone("Test Timezone", 345*60)
expectedValue = "+05:45" expectedValue = "+05:45"
actualValue = FormatTimezoneOffset(timezone) actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -720*60) timezone = time.FixedZone("Test Timezone", -720*60)
expectedValue = "-12:00" expectedValue = "-12:00"
actualValue = FormatTimezoneOffset(timezone) actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -150*60) timezone = time.FixedZone("Test Timezone", -150*60)
expectedValue = "-02:30" expectedValue = "-02:30"
actualValue = FormatTimezoneOffset(timezone) actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 0) timezone = time.FixedZone("Test Timezone", 0)
expectedValue = "+00:00" expectedValue = "+00:00"
actualValue = FormatTimezoneOffset(timezone) actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
}
func TestFormatTimezoneOffset_TimezoneWithDST(t *testing.T) {
londonLocation, err := time.LoadLocation("Europe/London")
assert.Equal(t, nil, err)
// during standard time (UTC+0)
expectedValue := "+00:00"
actualValue := FormatTimezoneOffset(1577858483, londonLocation) // 2020-01-01 06:01:23 +00:00
assert.Equal(t, expectedValue, actualValue)
// during daylight saving time (UTC+1)
expectedValue = "+01:00"
actualValue = FormatTimezoneOffset(1619845283, londonLocation) // 2021-05-01 06:01:23 +01:00
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
@@ -17,6 +17,7 @@ import type { TransactionReconciliationStatementResponseItem } from '@/models/tr
import { isArray } from '@/lib/common.ts'; import { isArray } from '@/lib/common.ts';
import { sumAmounts } from '@/lib/numeral.ts'; import { sumAmounts } from '@/lib/numeral.ts';
import { import {
parseDateTimeFromUnixTime,
getGregorianCalendarYearAndMonthFromUnixTime, getGregorianCalendarYearAndMonthFromUnixTime,
getYearFirstUnixTimeBySpecifiedUnixTime, getYearFirstUnixTimeBySpecifiedUnixTime,
getQuarterFirstUnixTimeBySpecifiedUnixTime, getQuarterFirstUnixTimeBySpecifiedUnixTime,
@@ -52,11 +53,11 @@ export interface CommonAccountBalanceTrendsChartProps {
export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) { export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) {
const { const {
formatUnixTimeToShortDate, formatDateTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear, formatDateTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth, formatDateTimeToGregorianLikeShortYearMonth,
formatUnixTimeToGregorianLikeYearQuarter, formatDateTimeToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear formatDateTimeToGregorianLikeFiscalYear
} = useI18n(); } = useI18n();
const dataDateRange = computed<AccountBalanceUnixTimeAndBalanceRange | null>(() => { const dataDateRange = computed<AccountBalanceUnixTimeAndBalanceRange | null>(() => {
@@ -150,19 +151,20 @@ export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTren
for (const dateRange of allDateRanges.value) { for (const dateRange of allDateRanges.value) {
const dataItems = dayDataItemsMap[dateRange.minUnixTime]; const dataItems = dayDataItemsMap[dateRange.minUnixTime];
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
let displayDate = ''; let displayDate = '';
if (props.dateAggregationType === ChartDateAggregationType.Year.type) { if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
displayDate = formatUnixTimeToGregorianLikeShortYear(dateRange.minUnixTime); displayDate = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) { } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
displayDate = formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime); displayDate = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) { } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
displayDate = formatUnixTimeToGregorianLikeYearQuarter(dateRange.minUnixTime); displayDate = formatDateTimeToGregorianLikeYearQuarter(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) { } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
displayDate = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime); displayDate = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) { } else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
displayDate = formatUnixTimeToShortDate(dateRange.minUnixTime); displayDate = formatDateTimeToShortDate(minDateTime);
} else { } else {
return ret; return ret;
} }
+31 -19
View File
@@ -1,16 +1,23 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { type TimeRangeAndDateType, type PresetDateRange, type UnixTimeRange, type WeekDayValue, DateRange } from '@/core/datetime.ts'; import {
type DateTime,
type UnixTimeRange,
type TimeRangeAndDateType,
type PresetDateRange,
type WeekDayValue,
DateRange,
} from '@/core/datetime.ts';
import { import {
getCurrentUnixTime, getCurrentUnixTime,
getLocalDatetimeFromUnixTime, getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime, getUnixTimeFromLocalDatetime,
getTodayFirstUnixTime, getTodayFirstUnixTime,
getDummyUnixTimeForLocalUsage, getSameDateTimeWithCurrentTimezone,
getActualUnixTimeForStore, getSameDateTimeWithBrowserTimezone,
getTimezoneOffsetMinutes, parseDateTimeFromUnixTime,
getBrowserTimezoneOffsetMinutes, parseDateTimeFromUnixTimeWithBrowserTimezone,
getDateRangeByDateType getDateRangeByDateType
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
@@ -45,23 +52,21 @@ function getDateRangeFromProps(props: CommonDateRangeSelectionProps): { minDate:
} }
export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps) { export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps) {
const { tt, formatUnixTimeToLongDateTime } = useI18n(); const { tt, formatDateTimeToLongDateTime } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const { minDate, maxDate } = getDateRangeFromProps(props); const { minDate, maxDate } = getDateRangeFromProps(props);
const dateRange = ref<Date[]>([ const dateRange = ref<Date[]>([
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(minDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())), getLocalDatetimeFromSameDateTimeOfUnixTime(minDate),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(maxDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())) getLocalDatetimeFromSameDateTimeOfUnixTime(maxDate)
]); ]);
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek); const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const beginDateTime = computed<string>(() => { const beginDateTime = computed<string>(() => {
const actualBeginUnixTime = getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(dateRange.value[0] as Date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()); return formatDateTimeToLongDateTime(getDateTimeFromSameDateTimeOfLocalDatetime(dateRange.value[0] as Date));
return formatUnixTimeToLongDateTime(actualBeginUnixTime);
}); });
const endDateTime = computed<string>(() => { const endDateTime = computed<string>(() => {
const actualEndUnixTime = getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(dateRange.value[1] as Date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()); return formatDateTimeToLongDateTime(getDateTimeFromSameDateTimeOfLocalDatetime(dateRange.value[1] as Date));
return formatUnixTimeToLongDateTime(actualEndUnixTime);
}); });
const presetRanges = computed<PresetDateRange[]>(() => { const presetRanges = computed<PresetDateRange[]>(() => {
const presetRanges:PresetDateRange[] = []; const presetRanges:PresetDateRange[] = [];
@@ -82,8 +87,8 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
presetRanges.push({ presetRanges.push({
label: tt(dateRangeType.name), label: tt(dateRangeType.name),
value: [ value: [
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())), getLocalDatetimeFromSameDateTimeOfUnixTime(dateRange.minTime),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())) getLocalDatetimeFromSameDateTimeOfUnixTime(dateRange.maxTime)
] ]
}); });
}); });
@@ -91,6 +96,14 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
return presetRanges; return presetRanges;
}); });
function getLocalDatetimeFromSameDateTimeOfUnixTime(unixTime: number): Date {
return getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(unixTime)).getUnixTime());
}
function getDateTimeFromSameDateTimeOfLocalDatetime(localDatetime: Date): DateTime {
return getSameDateTimeWithCurrentTimezone(parseDateTimeFromUnixTimeWithBrowserTimezone(getUnixTimeFromLocalDatetime(localDatetime)));
}
function getFinalDateRange(): UnixTimeRange | null { function getFinalDateRange(): UnixTimeRange | null {
if (!dateRange.value[0] || !dateRange.value[1]) { if (!dateRange.value[0] || !dateRange.value[1]) {
return null; return null;
@@ -99,16 +112,13 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
const currentMinDate = dateRange.value[0]; const currentMinDate = dateRange.value[0];
const currentMaxDate = dateRange.value[1]; const currentMaxDate = dateRange.value[1];
let minUnixTime = getUnixTimeFromLocalDatetime(currentMinDate); const minUnixTime = getDateTimeFromSameDateTimeOfLocalDatetime(currentMinDate).getUnixTime();
let maxUnixTime = getUnixTimeFromLocalDatetime(currentMaxDate); const maxUnixTime = getDateTimeFromSameDateTimeOfLocalDatetime(currentMaxDate).getUnixTime();
if (minUnixTime < 0 || maxUnixTime < 0) { if (minUnixTime < 0 || maxUnixTime < 0) {
throw new Error('Date is too early'); throw new Error('Date is too early');
} }
minUnixTime = getActualUnixTimeForStore(minUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
maxUnixTime = getActualUnixTimeForStore(maxUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return { return {
minUnixTime, minUnixTime,
maxUnixTime maxUnixTime
@@ -123,6 +133,8 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
endDateTime, endDateTime,
presetRanges, presetRanges,
// functions // functions
getLocalDatetimeFromSameDateTimeOfUnixTime,
getDateTimeFromSameDateTimeOfLocalDatetime,
getFinalDateRange getFinalDateRange
}; };
} }
@@ -5,6 +5,15 @@ import { useI18n } from '@/locales/helpers.ts';
import { type NameValue } from '@/core/base.ts'; import { type NameValue } from '@/core/base.ts';
import { NumeralSystem } from '@/core/numeral.ts'; import { NumeralSystem } from '@/core/numeral.ts';
import {
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getSameDateTimeWithBrowserTimezone,
getSameDateTimeWithTimezoneOffset,
parseDateTimeFromUnixTimeWithBrowserTimezone,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
export interface TimePickerValue { export interface TimePickerValue {
value: string; value: string;
itemsIndex: number; itemsIndex: number;
@@ -30,6 +39,14 @@ export function useDateTimeSelectionBase() {
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType()); const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const meridiemItems = computed<NameValue[]>(() => getAllMeridiemIndicators()); const meridiemItems = computed<NameValue[]>(() => getAllMeridiemIndicators());
function getLocalDatetimeFromSameDateTimeOfUnixTime(unixTime: number, utcOffset: number): Date {
return getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTimeWithTimezoneOffset(unixTime, utcOffset)).getUnixTime());
}
function getUnixTimeFromSameDateTimeOfLocalDatetime(localDatetime: Date, utcOffset: number): number {
return getSameDateTimeWithTimezoneOffset(parseDateTimeFromUnixTimeWithBrowserTimezone(getUnixTimeFromLocalDatetime(localDatetime)), utcOffset).getUnixTime();
}
function getDisplayTimeValue(value: number, forceTwoDigits: boolean): string { function getDisplayTimeValue(value: number, forceTwoDigits: boolean): string {
let textualValue = value.toString(); let textualValue = value.toString();
@@ -89,6 +106,8 @@ export function useDateTimeSelectionBase() {
// computed // computed
meridiemItems, meridiemItems,
// functions // functions
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue, getDisplayTimeValue,
generateAllHours, generateAllHours,
generateAllMinutesOrSeconds generateAllMinutesOrSeconds
@@ -7,6 +7,7 @@ import {
getYear0BasedMonthObjectFromString, getYear0BasedMonthObjectFromString,
getYearMonthStringFromYear0BasedMonthObject, getYearMonthStringFromYear0BasedMonthObject,
getCurrentUnixTime, getCurrentUnixTime,
parseDateTimeFromUnixTime,
getThisYearFirstUnixTime, getThisYearFirstUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime getYearMonthLastUnixTime
@@ -49,7 +50,7 @@ function getMonthRangeFromProps(props: CommonMonthRangeSelectionProps): { minDat
} }
export function useMonthRangeSelectionBase(props: CommonMonthRangeSelectionProps) { export function useMonthRangeSelectionBase(props: CommonMonthRangeSelectionProps) {
const { formatUnixTimeToGregorianLikeLongYearMonth } = useI18n(); const { formatDateTimeToGregorianLikeLongYearMonth } = useI18n();
const { minDate, maxDate } = getMonthRangeFromProps(props); const { minDate, maxDate } = getMonthRangeFromProps(props);
const dateRange = ref<Year0BasedMonth[]>([ const dateRange = ref<Year0BasedMonth[]>([
@@ -57,8 +58,8 @@ export function useMonthRangeSelectionBase(props: CommonMonthRangeSelectionProps
maxDate maxDate
]); ]);
const beginDateTime = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(dateRange.value[0] as Year0BasedMonth))); const beginDateTime = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthFirstUnixTime(dateRange.value[0] as Year0BasedMonth))));
const endDateTime = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthLastUnixTime(dateRange.value[1] as Year0BasedMonth))); const endDateTime = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthLastUnixTime(dateRange.value[1] as Year0BasedMonth))));
function getFinalMonthRange(): { minYearMonth: TextualYearMonth | '', maxYearMonth: TextualYearMonth | '' } | null { function getFinalMonthRange(): { minYearMonth: TextualYearMonth | '', maxYearMonth: TextualYearMonth | '' } | null {
if (!dateRange.value[0] || !dateRange.value[1]) { if (!dateRange.value[0] || !dateRange.value[1]) {
+8 -8
View File
@@ -88,9 +88,9 @@ const {
getCurrentNumeralSystemType, getCurrentNumeralSystemType,
isLongDateMonthAfterYear, isLongDateMonthAfterYear,
isLongTime24HourFormat, isLongTime24HourFormat,
getCalendarDisplayShortYearFromUnixTime, getCalendarDisplayShortYearFromDateTime,
getCalendarDisplayShortMonthFromUnixTime, getCalendarDisplayShortMonthFromDateTime,
getCalendarDisplayDayOfMonthFromUnixTime, getCalendarDisplayDayOfMonthFromDateTime,
getCalendarAlternateDate getCalendarAlternateDate
} = useI18n(); } = useI18n();
@@ -138,21 +138,21 @@ function switchView(viewType: MenuView): void {
} }
function getDisplayYear(year: number): string { function getDisplayYear(year: number): string {
return getCalendarDisplayShortYearFromUnixTime(getYearMonthDayDateTime(year, 1, 1).getUnixTime(), actualNumeralSystem.value); return getCalendarDisplayShortYearFromDateTime(getYearMonthDayDateTime(year, 1, 1), actualNumeralSystem.value);
} }
function getDisplayMonth(month: number): string { function getDisplayMonth(month: number): string {
if (isArray(dateTime.value)) { if (isArray(dateTime.value)) {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value[0]!.getFullYear(), month + 1, 1).getUnixTime(), actualNumeralSystem.value); return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value[0]!.getFullYear(), month + 1, 1), actualNumeralSystem.value);
} else if (dateTime.value) { } else if (dateTime.value) {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value.getFullYear(), month + 1, 1).getUnixTime(), actualNumeralSystem.value); return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value.getFullYear(), month + 1, 1), actualNumeralSystem.value);
} else { } else {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(new Date().getFullYear(), month + 1, 1).getUnixTime(), actualNumeralSystem.value); return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(new Date().getFullYear(), month + 1, 1), actualNumeralSystem.value);
} }
} }
function getDisplayDay(date: Date): string { function getDisplayDay(date: Date): string {
return getCalendarDisplayDayOfMonthFromUnixTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()).getUnixTime(), actualNumeralSystem.value); return getCalendarDisplayDayOfMonthFromDateTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()), actualNumeralSystem.value);
} }
defineExpose({ defineExpose({
+5 -5
View File
@@ -54,8 +54,8 @@ const emit = defineEmits<{
const { const {
isLongDateMonthAfterYear, isLongDateMonthAfterYear,
getCalendarDisplayShortYearFromUnixTime, getCalendarDisplayShortYearFromDateTime,
getCalendarDisplayShortMonthFromUnixTime getCalendarDisplayShortMonthFromDateTime
} = useI18n(); } = useI18n();
const yearRange = getAllowedYearRange(); const yearRange = getAllowedYearRange();
@@ -96,14 +96,14 @@ function getYear0BasedMonthFromMonthSelectionValue(value: MonthSelectionValue):
} }
function getDisplayYear(year: number): string { function getDisplayYear(year: number): string {
return getCalendarDisplayShortYearFromUnixTime(getYearMonthDayDateTime(year, 1, 1).getUnixTime()); return getCalendarDisplayShortYearFromDateTime(getYearMonthDayDateTime(year, 1, 1));
} }
function getDisplayMonth(month: number): string { function getDisplayMonth(month: number): string {
if (isArray(dateTime.value)) { if (isArray(dateTime.value)) {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value[0]!.year, month + 1, 1).getUnixTime()); return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value[0]!.year, month + 1, 1));
} else { } else {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value.year, month + 1, 1).getUnixTime()); return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value.year, month + 1, 1));
} }
} }
</script> </script>
+4 -12
View File
@@ -38,14 +38,7 @@ import type { CalendarAlternateDate, TextualYearMonthDay, WeekDayValue } from '@
import { INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numeral.ts'; import { INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numeral.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts'; import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import { import { getYearMonthDayDateTime } from '@/lib/datetime.ts';
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getUnixTimeFromLocalDatetime,
getActualUnixTimeForStore,
getYearMonthDayDateTime,
parseDateTimeFromUnixTime
} from '@/lib/datetime.ts';
const props = defineProps<{ const props = defineProps<{
modelValue: TextualYearMonthDay | ''; modelValue: TextualYearMonthDay | '';
@@ -67,7 +60,7 @@ const emit = defineEmits<{
const { const {
getAllLongWeekdayNames, getAllLongWeekdayNames,
getAllShortWeekdayNames, getAllShortWeekdayNames,
getCalendarDisplayDayOfMonthFromUnixTime, getCalendarDisplayDayOfMonthFromDateTime,
getCalendarAlternateDates, getCalendarAlternateDates,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -105,8 +98,7 @@ const alternateDates = computed<Record<TextualYearMonthDay, string> | undefined>
}); });
function noTransactionInMonthDay(date: Date): boolean { function noTransactionInMonthDay(date: Date): boolean {
const dateTime = parseDateTimeFromUnixTime(getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); return !props.dailyTotalAmounts || !props.dailyTotalAmounts[date.getDate()];
return !props.dailyTotalAmounts || !props.dailyTotalAmounts[dateTime.getGregorianCalendarDay()];
} }
function getDisplayMonthTotalAmount(amount: number, currency: string | false, symbol: string, incomplete: boolean): string { function getDisplayMonthTotalAmount(amount: number, currency: string | false, symbol: string, incomplete: boolean): string {
@@ -115,7 +107,7 @@ function getDisplayMonthTotalAmount(amount: number, currency: string | false, sy
} }
function getDisplayDay(date: Date): string { function getDisplayDay(date: Date): string {
return getCalendarDisplayDayOfMonthFromUnixTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()).getUnixTime()); return getCalendarDisplayDayOfMonthFromDateTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()));
} }
</script> </script>
@@ -43,13 +43,6 @@ import { type CommonDateRangeSelectionProps, useDateRangeSelectionBase } from '@
import { ThemeType } from '@/core/theme.ts'; import { ThemeType } from '@/core/theme.ts';
import {
getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes
} from '@/lib/datetime.ts';
interface DesktopDateRangeSelectionProps extends CommonDateRangeSelectionProps { interface DesktopDateRangeSelectionProps extends CommonDateRangeSelectionProps {
persistent?: boolean; persistent?: boolean;
} }
@@ -64,7 +57,14 @@ const emit = defineEmits<{
const theme = useTheme(); const theme = useTheme();
const { tt } = useI18n(); const { tt } = useI18n();
const { dateRange, beginDateTime, endDateTime, presetRanges, getFinalDateRange } = useDateRangeSelectionBase(props); const {
dateRange,
beginDateTime,
endDateTime,
presetRanges,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getFinalDateRange
} = useDateRangeSelectionBase(props);
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark); const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
const showState = computed<boolean>({ const showState = computed<boolean>({
@@ -94,13 +94,13 @@ function cancel(): void {
watch(() => props.minTime, (newValue) => { watch(() => props.minTime, (newValue) => {
if (newValue) { if (newValue) {
dateRange.value[0] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(newValue, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateRange.value[0] = getLocalDatetimeFromSameDateTimeOfUnixTime(newValue);
} }
}); });
watch(() => props.maxTime, (newValue) => { watch(() => props.maxTime, (newValue) => {
if (newValue) { if (newValue) {
dateRange.value[1] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(newValue, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateRange.value[1] = getLocalDatetimeFromSameDateTimeOfUnixTime(newValue);
} }
}); });
</script> </script>
+12 -12
View File
@@ -94,12 +94,9 @@ import {
} from '@/core/datetime.ts'; } from '@/core/datetime.ts';
import { import {
getHourIn12HourFormat, getHourIn12HourFormat,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime, getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime, getSameDateTimeWithBrowserTimezone,
getActualUnixTimeForStore, parseDateTimeFromUnixTimeWithTimezoneOffset,
getDummyUnixTimeForLocalUsage,
parseDateTimeFromKnownDateTimeFormat, parseDateTimeFromKnownDateTimeFormat,
getAMOrPM, getAMOrPM,
getCombinedDateAndTimeValues getCombinedDateAndTimeValues
@@ -108,6 +105,7 @@ import { setChildInputFocus } from '@/lib/ui/desktop.ts';
const props = defineProps<{ const props = defineProps<{
modelValue: number; modelValue: number;
timezoneUtcOffset: number;
disabled?: boolean; disabled?: boolean;
readonly?: boolean; readonly?: boolean;
label?: string; label?: string;
@@ -124,7 +122,7 @@ const {
getCurrentNumeralSystemType, getCurrentNumeralSystemType,
parseDateTimeFromLongDateTime, parseDateTimeFromLongDateTime,
parseDateTimeFromShortDateTime, parseDateTimeFromShortDateTime,
formatUnixTimeToLongDateTime formatDateTimeToLongDateTime
} = useI18n(); } = useI18n();
const { const {
@@ -133,6 +131,8 @@ const {
isMinuteTwoDigits, isMinuteTwoDigits,
isSecondTwoDigits, isSecondTwoDigits,
isMeridiemIndicatorFirst, isMeridiemIndicatorFirst,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue, getDisplayTimeValue,
generateAllHours, generateAllHours,
generateAllMinutesOrSeconds generateAllMinutesOrSeconds
@@ -147,10 +147,10 @@ const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType(
const dateTime = computed<Date>({ const dateTime = computed<Date>({
get: () => { get: () => {
return getLocalDatetimeFromUnixTime(props.modelValue); return getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue, props.timezoneUtcOffset);
}, },
set: (value: Date) => { set: (value: Date) => {
const unixTime = getUnixTimeFromLocalDatetime(value); const unixTime = getUnixTimeFromSameDateTimeOfLocalDatetime(value, props.timezoneUtcOffset);
if (unixTime < 0) { if (unixTime < 0) {
emit('error', 'Date is too early'); emit('error', 'Date is too early');
@@ -161,7 +161,7 @@ const dateTime = computed<Date>({
} }
}); });
const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(dateTime.value), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))); const displayTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTimeWithTimezoneOffset(props.modelValue, props.timezoneUtcOffset)));
const hourItems = computed<TimePickerValue[]>(() => generateAllHours(1, isHourTwoDigits.value)); const hourItems = computed<TimePickerValue[]>(() => generateAllHours(1, isHourTwoDigits.value));
const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isMinuteTwoDigits.value)); const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isMinuteTwoDigits.value));
@@ -252,7 +252,7 @@ function onPaste(event: ClipboardEvent): void {
dt = parseDateTimeFromKnownDateTimeFormat(text, formats[0] as KnownDateTimeFormat); dt = parseDateTimeFromKnownDateTimeFormat(text, formats[0] as KnownDateTimeFormat);
if (dt) { if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return; return;
} }
} }
@@ -260,14 +260,14 @@ function onPaste(event: ClipboardEvent): void {
dt = parseDateTimeFromLongDateTime(text); dt = parseDateTimeFromLongDateTime(text);
if (dt) { if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return; return;
} }
dt = parseDateTimeFromShortDateTime(text); dt = parseDateTimeFromShortDateTime(text);
if (dt) { if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return; return;
} }
+11 -8
View File
@@ -43,6 +43,7 @@ import {
isNumber isNumber
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import {
parseDateTimeFromUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime, getYearMonthLastUnixTime,
getDateTypeByDateRange, getDateTypeByDateRange,
@@ -95,11 +96,11 @@ const theme = useTheme();
const { const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
formatUnixTimeToShortDate, formatDateTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear, formatDateTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth, formatDateTimeToGregorianLikeShortYearMonth,
formatYearQuarterToGregorianLikeYearQuarter, formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear, formatDateTimeToGregorianLikeFiscalYear,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping, formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -151,16 +152,18 @@ const allDisplayDateRanges = computed<string[]>(() => {
const allDisplayDateRanges: string[] = []; const allDisplayDateRanges: string[] = [];
for (const dateRange of allDateRanges.value) { for (const dateRange of allDateRanges.value) {
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
if (props.dateAggregationType === ChartDateAggregationType.Year.type) { if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeShortYear(dateRange.minUnixTime)); allDisplayDateRanges.push(formatDateTimeToGregorianLikeShortYear(minDateTime));
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type && 'year' in dateRange) { } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type && 'year' in dateRange) {
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime)); allDisplayDateRanges.push(formatDateTimeToGregorianLikeFiscalYear(minDateTime));
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) { } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
allDisplayDateRanges.push(formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter)); allDisplayDateRanges.push(formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter));
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) { } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime)); allDisplayDateRanges.push(formatDateTimeToGregorianLikeShortYearMonth(minDateTime));
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') { } else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
allDisplayDateRanges.push(formatUnixTimeToShortDate(dateRange.minUnixTime)); allDisplayDateRanges.push(formatDateTimeToShortDate(minDateTime));
} }
} }
@@ -45,13 +45,6 @@ import { type CommonDateRangeSelectionProps, useDateRangeSelectionBase } from '@
import { useEnvironmentsStore } from '@/stores/environment.ts'; import { useEnvironmentsStore } from '@/stores/environment.ts';
import {
getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes
} from '@/lib/datetime.ts';
type DateTimePickerType = InstanceType<typeof DateTimePicker>; type DateTimePickerType = InstanceType<typeof DateTimePicker>;
const props = defineProps<CommonDateRangeSelectionProps>(); const props = defineProps<CommonDateRangeSelectionProps>();
@@ -62,7 +55,14 @@ const emit = defineEmits<{
const { tt } = useI18n(); const { tt } = useI18n();
const { showToast } = useI18nUIComponents(); const { showToast } = useI18nUIComponents();
const { dateRange, beginDateTime, endDateTime, presetRanges, getFinalDateRange } = useDateRangeSelectionBase(props); const {
dateRange,
beginDateTime,
endDateTime,
presetRanges,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getFinalDateRange
} = useDateRangeSelectionBase(props);
const environmentsStore = useEnvironmentsStore(); const environmentsStore = useEnvironmentsStore();
@@ -91,11 +91,11 @@ function cancel(): void {
function onSheetOpen(): void { function onSheetOpen(): void {
if (props.minTime) { if (props.minTime) {
dateRange.value[0] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(props.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateRange.value[0] = getLocalDatetimeFromSameDateTimeOfUnixTime(props.minTime);
} }
if (props.maxTime) { if (props.maxTime) {
dateRange.value[1] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(props.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); dateRange.value[1] = getLocalDatetimeFromSameDateTimeOfUnixTime(props.maxTime);
} }
window.dispatchEvent(new Event('resize')); // fix vue-datepicker preset max-width window.dispatchEvent(new Event('resize')); // fix vue-datepicker preset max-width
@@ -111,9 +111,7 @@ import { NumeralSystem } from '@/core/numeral.ts';
import { isDefined } from '@/lib/common.ts'; import { isDefined } from '@/lib/common.ts';
import { import {
getHourIn12HourFormat, getHourIn12HourFormat,
getLocalDatetimeFromUnixTime,
getCurrentUnixTime, getCurrentUnixTime,
getUnixTimeFromLocalDatetime,
getAMOrPM, getAMOrPM,
getCombinedDateAndTimeValues getCombinedDateAndTimeValues
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
@@ -122,6 +120,7 @@ type DateTimePickerType = InstanceType<typeof DateTimePicker>;
const props = defineProps<{ const props = defineProps<{
modelValue: number; modelValue: number;
timezoneUtcOffset: number;
initMode?: string; initMode?: string;
show: boolean; show: boolean;
}>(); }>();
@@ -144,6 +143,8 @@ const {
isSecondTwoDigits, isSecondTwoDigits,
isMeridiemIndicatorFirst, isMeridiemIndicatorFirst,
meridiemItems, meridiemItems,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue, getDisplayTimeValue,
generateAllHours, generateAllHours,
generateAllMinutesOrSeconds generateAllMinutesOrSeconds
@@ -160,7 +161,7 @@ let resetTimePickerItemPositionItemsLastOffsetTop: number | undefined = undefine
let resetTimePickerItemPositionCheckedFrames: number | undefined = undefined; let resetTimePickerItemPositionCheckedFrames: number | undefined = undefined;
const mode = ref<string>(props.initMode || 'time'); const mode = ref<string>(props.initMode || 'time');
const dateTime = ref<Date>(getLocalDatetimeFromUnixTime(props.modelValue || getCurrentUnixTime())); const dateTime = ref<Date>(getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue || getCurrentUnixTime(), props.timezoneUtcOffset));
const timePickerContainerHeight = ref<number | undefined>(undefined); const timePickerContainerHeight = ref<number | undefined>(undefined);
const timePickerItemHeight = ref<number | undefined>(undefined); const timePickerItemHeight = ref<number | undefined>(undefined);
@@ -213,7 +214,7 @@ function switchMode(): void {
} }
function setCurrentTime(): void { function setCurrentTime(): void {
dateTime.value = getLocalDatetimeFromUnixTime(getCurrentUnixTime()); dateTime.value = getLocalDatetimeFromSameDateTimeOfUnixTime(getCurrentUnixTime(), props.timezoneUtcOffset);
if (mode.value === 'time') { if (mode.value === 'time') {
scrollAllTimeSelectedItems(); scrollAllTimeSelectedItems();
@@ -225,7 +226,7 @@ function confirm(): void {
return; return;
} }
const unixTime = getUnixTimeFromLocalDatetime(dateTime.value); const unixTime = getUnixTimeFromSameDateTimeOfLocalDatetime(dateTime.value, props.timezoneUtcOffset);
if (unixTime < 0) { if (unixTime < 0) {
showToast('Date is too early'); showToast('Date is too early');
@@ -420,7 +421,7 @@ function onSheetOpen(): void {
mode.value = props.initMode || 'time'; mode.value = props.initMode || 'time';
if (props.modelValue) { if (props.modelValue) {
dateTime.value = getLocalDatetimeFromUnixTime(props.modelValue); dateTime.value = getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue, props.timezoneUtcOffset);
} }
if (mode.value === 'time') { if (mode.value === 'time') {
+10 -8
View File
@@ -144,6 +144,7 @@ import {
isNumber isNumber
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import {
parseDateTimeFromUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime, getYearMonthLastUnixTime,
getDateTypeByDateRange, getDateTypeByDateRange,
@@ -200,11 +201,11 @@ const emit = defineEmits<{
const { const {
tt, tt,
formatUnixTimeToShortDate, formatDateTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear, formatDateTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth, formatDateTimeToGregorianLikeShortYearMonth,
formatYearQuarterToGregorianLikeYearQuarter, formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear, formatDateTimeToGregorianLikeFiscalYear,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -324,18 +325,19 @@ const allDisplayDataItems = computed<TrendsBarChartData>(() => {
dateRangeKey = `${dateRange.year}-${dateRange.month}-${dateRange.day}`; dateRangeKey = `${dateRange.year}-${dateRange.month}-${dateRange.day}`;
} }
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
let displayDateRange = ''; let displayDateRange = '';
if (props.dateAggregationType === ChartDateAggregationType.Year.type) { if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
displayDateRange = formatUnixTimeToGregorianLikeShortYear(dateRange.minUnixTime); displayDateRange = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) { } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
displayDateRange = formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime); displayDateRange = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) { } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
displayDateRange = formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter); displayDateRange = formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) { } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
displayDateRange = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime); displayDateRange = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') { } else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
displayDateRange = formatUnixTimeToShortDate(dateRange.minUnixTime); displayDateRange = formatDateTimeToShortDate(minDateTime);
} }
const dataItems = allDateRangeItemsMap[dateRangeKey] || []; const dataItems = allDateRangeItemsMap[dateRangeKey] || [];
+90 -27
View File
@@ -69,7 +69,31 @@ interface DateTimeFormatResult {
hasNumeral?: boolean; hasNumeral?: boolean;
} }
type DateTimeTokenFormatFunction = (d: MomentDateTime, options: DateTimeFormatOptions) => DateTimeFormatResult type DateTimeTokenFormatFunction = (d: MomentDateTime, options: DateTimeFormatOptions) => DateTimeFormatResult;
const westernmostTimezoneUtcOffset: number = -720; // Etc/GMT+12 (UTC-12:00)
const easternmostTimezoneUtcOffset: number = 840; // Pacific/Kiritimati (UTC+14:00)
function getFixedTimezoneName(utcOffset: number): string {
return `Fixed/Timezone${utcOffset}`;
}
(function initFixedTimezone(): void {
for (let utcOffset = westernmostTimezoneUtcOffset; utcOffset <= easternmostTimezoneUtcOffset; utcOffset += 15) {
const timezoneName = getFixedTimezoneName(utcOffset);
if (moment.tz.zone(timezoneName)) {
continue;
}
moment.tz.add(moment.tz.pack({
name: timezoneName,
abbrs: [`FIX${utcOffset}`],
offsets: [-utcOffset],
untils: [0]
}));
}
})();
class MomentDateTime implements DateTime { class MomentDateTime implements DateTime {
private static readonly tokenFormatFuncs: Record<string, DateTimeTokenFormatFunction> = { private static readonly tokenFormatFuncs: Record<string, DateTimeTokenFormatFunction> = {
@@ -501,28 +525,29 @@ export function getUtcOffsetByUtcOffsetMinutes(utcOffsetMinutes: number): string
} }
} }
export function getTimezoneOffset(timezone?: string): string { export function getTimezoneOffset(unixTime: number, timezone?: string): string {
return getUtcOffsetByUtcOffsetMinutes(getTimezoneOffsetMinutes(timezone)); return getUtcOffsetByUtcOffsetMinutes(getTimezoneOffsetMinutes(unixTime, timezone));
} }
export function getTimezoneOffsetMinutes(timezone?: string): number { export function getTimezoneOffsetMinutes(unixTime: number, timezone?: string): number {
if (timezone) { if (timezone) {
return moment().tz(timezone).utcOffset(); return moment.unix(unixTime).tz(timezone).utcOffset();
} else { } else {
return moment().utcOffset(); return moment.unix(unixTime).utcOffset();
} }
} }
export function getBrowserTimezoneOffset(): string { export function getBrowserTimezoneOffset(unixTime: number): string {
return getUtcOffsetByUtcOffsetMinutes(getBrowserTimezoneOffsetMinutes()); return getUtcOffsetByUtcOffsetMinutes(getBrowserTimezoneOffsetMinutes(unixTime));
} }
export function getBrowserTimezoneOffsetMinutes(): number { export function getBrowserTimezoneOffsetMinutes(unixTime: number): number {
return -new Date().getTimezoneOffset(); const date = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(unixTime)).getUnixTime());
return -date.getTimezoneOffset();
} }
export function guessTimezoneName(): string { export function getBrowserTimezoneName(): string {
return moment.tz.guess(true); return new Intl.DateTimeFormat().resolvedOptions().timeZone;
} }
export function getLocalDatetimeFromUnixTime(unixTime: number): Date { export function getLocalDatetimeFromUnixTime(unixTime: number): Date {
@@ -533,12 +558,46 @@ export function getUnixTimeFromLocalDatetime(datetime: Date): number {
return Math.floor(datetime.getTime() / 1000); return Math.floor(datetime.getTime() / 1000);
} }
export function getActualUnixTimeForStore(unixTime: number, utcOffset: number, currentUtcOffset: number): number { export function getSameDateTimeWithCurrentTimezone(dateTime: DateTime): DateTime {
return unixTime - (utcOffset - currentUtcOffset) * 60; const newDateTime = moment().set({
year: dateTime.getGregorianCalendarYear(),
month: dateTime.getGregorianCalendarMonth() - 1,
date: dateTime.getGregorianCalendarDay(),
hour: dateTime.getHour(),
minute: dateTime.getMinute(),
second: dateTime.getSecond(),
millisecond: 0
});
return MomentDateTime.of(newDateTime);
} }
export function getDummyUnixTimeForLocalUsage(unixTime: number, utcOffset: number, currentUtcOffset: number): number { export function getSameDateTimeWithBrowserTimezone(dateTime: DateTime): DateTime {
return unixTime + (utcOffset - currentUtcOffset) * 60; const newDateTime = moment().tz(getBrowserTimezoneName()).set({
year: dateTime.getGregorianCalendarYear(),
month: dateTime.getGregorianCalendarMonth() - 1,
date: dateTime.getGregorianCalendarDay(),
hour: dateTime.getHour(),
minute: dateTime.getMinute(),
second: dateTime.getSecond(),
millisecond: 0,
});
return MomentDateTime.of(newDateTime);
}
export function getSameDateTimeWithTimezoneOffset(dateTime: DateTime, utcOffset: number): DateTime {
const newDateTime = moment().tz(getFixedTimezoneName(utcOffset)).set({
year: dateTime.getGregorianCalendarYear(),
month: dateTime.getGregorianCalendarMonth() - 1,
date: dateTime.getGregorianCalendarDay(),
hour: dateTime.getHour(),
minute: dateTime.getMinute(),
second: dateTime.getSecond(),
millisecond: 0
});
return MomentDateTime.of(newDateTime);
} }
export function getCurrentDateTime(): DateTime { export function getCurrentDateTime(): DateTime {
@@ -554,18 +613,18 @@ export function getYearMonthDayDateTime(year: number, month: number, day: number
return MomentDateTime.of(date); return MomentDateTime.of(date);
} }
export function parseDateTimeFromUnixTime(unixTime: number, utcOffset?: number, currentUtcOffset?: number): DateTime { export function parseDateTimeFromUnixTime(unixTime: number): DateTime {
if (isNumber(utcOffset)) {
if (!isNumber(currentUtcOffset)) {
currentUtcOffset = getTimezoneOffsetMinutes();
}
unixTime = getDummyUnixTimeForLocalUsage(unixTime, utcOffset, currentUtcOffset);
}
return MomentDateTime.of(moment.unix(unixTime)); return MomentDateTime.of(moment.unix(unixTime));
} }
export function parseDateTimeFromUnixTimeWithBrowserTimezone(unixTime: number): DateTime {
return MomentDateTime.of(moment.unix(unixTime).tz(getBrowserTimezoneName()));
}
export function parseDateTimeFromUnixTimeWithTimezoneOffset(unixTime: number, utcOffset: number): DateTime {
return MomentDateTime.of(moment.unix(unixTime).tz(getFixedTimezoneName(utcOffset)));
}
export function parseDateTimeFromKnownDateTimeFormat(dateTime: string, format: KnownDateTimeFormat): DateTime | undefined { export function parseDateTimeFromKnownDateTimeFormat(dateTime: string, format: KnownDateTimeFormat): DateTime | undefined {
const m = moment(dateTime, format.format); const m = moment(dateTime, format.format);
@@ -586,8 +645,12 @@ export function parseDateTimeFromString(dateTime: string, format: string): DateT
return MomentDateTime.of(m); return MomentDateTime.of(m);
} }
export function formatUnixTime(unixTime: number, format: string, options: DateTimeFormatOptions, utcOffset?: number, currentUtcOffset?: number): string { export function formatDateTime(dateTime: DateTime, format: string, options: DateTimeFormatOptions): string {
return parseDateTimeFromUnixTime(unixTime, utcOffset, currentUtcOffset).format(format, options); return dateTime.format(format, options);
}
export function formatUnixTime(unixTime: number, format: string, options: DateTimeFormatOptions): string {
return parseDateTimeFromUnixTime(unixTime).format(format, options);
} }
export function formatCurrentTime(format: string, options: DateTimeFormatOptions): string { export function formatCurrentTime(format: string, options: DateTimeFormatOptions): string {
+4 -3
View File
@@ -178,7 +178,8 @@ import {
} from './server_settings.ts'; } from './server_settings.ts';
import { import {
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
guessTimezoneName getBrowserTimezoneName,
getCurrentUnixTime
} from './datetime.ts'; } from './datetime.ts';
import { generateRandomUUID } from './misc.ts'; import { generateRandomUUID } from './misc.ts';
import { getBasePath } from './web.ts'; import { getBasePath } from './web.ts';
@@ -208,12 +209,12 @@ axios.interceptors.request.use((config: ApiRequestConfig) => {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes(); config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes(getCurrentUnixTime());
let timezoneName = getTimeZone(); let timezoneName = getTimeZone();
if (!timezoneName || timezoneName.trim().length < 1) { if (!timezoneName || timezoneName.trim().length < 1) {
timezoneName = guessTimezoneName(); timezoneName = getBrowserTimezoneName();
} }
config.headers['X-Timezone-Name'] = timezoneName; config.headers['X-Timezone-Name'] = timezoneName;
+5 -10
View File
@@ -11,8 +11,7 @@ import {
isNumber isNumber
} from './common.ts'; } from './common.ts';
import { import {
getBrowserTimezoneOffsetMinutes, getTimezoneOffsetMinutes
getDummyUnixTimeForLocalUsage
} from './datetime.ts'; } from './datetime.ts';
import { import {
categoryTypeToTransactionType, categoryTypeToTransactionType,
@@ -33,9 +32,10 @@ export interface SetTransactionOptions {
comment?: string; comment?: string;
} }
export function setTransactionModelByTransaction(transaction: Transaction, transaction2: Transaction | null | undefined, allCategories: Record<number, TransactionCategory[]>, allCategoriesMap: Record<string, TransactionCategory>, allVisibleAccounts: Account[], allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>, defaultAccountId: string, options: SetTransactionOptions, setContextData: boolean, convertContextTime: boolean): void { export function setTransactionModelByTransaction(transaction: Transaction, transaction2: Transaction | null | undefined, allCategories: Record<number, TransactionCategory[]>, allCategoriesMap: Record<string, TransactionCategory>, allVisibleAccounts: Account[], allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>, defaultAccountId: string, options: SetTransactionOptions, setContextData: boolean): void {
if (isDefined(options.time)) { if (isDefined(options.time)) {
transaction.time = options.time; transaction.time = options.time;
transaction.utcOffset = getTimezoneOffsetMinutes(transaction.time, transaction.timeZone);
} }
if (!options.type && options.categoryId && options.categoryId !== '0' && allCategoriesMap[options.categoryId]) { if (!options.type && options.categoryId && options.categoryId !== '0' && allCategoriesMap[options.categoryId]) {
@@ -172,14 +172,9 @@ export function setTransactionModelByTransaction(transaction: Transaction, trans
} }
if (setContextData) { if (setContextData) {
transaction.utcOffset = transaction2.utcOffset; transaction.time = transaction2.time;
transaction.timeZone = transaction2.timeZone; transaction.timeZone = transaction2.timeZone;
transaction.utcOffset = transaction2.utcOffset;
if (convertContextTime) {
transaction.time = getDummyUnixTimeForLocalUsage(transaction2.time, transaction.utcOffset, getBrowserTimezoneOffsetMinutes());
} else {
transaction.time = transaction2.time;
}
} }
transaction.sourceAccountId = transaction2.sourceAccountId; transaction.sourceAccountId = transaction2.sourceAccountId;
+37 -45
View File
@@ -187,6 +187,7 @@ import {
formatCurrentTime, formatCurrentTime,
formatGregorianCalendarYearDashMonthDashDay, formatGregorianCalendarYearDashMonthDashDay,
formatGregorianCalendarMonthDashDay, formatGregorianCalendarMonthDashDay,
formatDateTime,
formatUnixTime, formatUnixTime,
getBrowserTimezoneOffset, getBrowserTimezoneOffset,
getBrowserTimezoneOffsetMinutes, getBrowserTimezoneOffsetMinutes,
@@ -202,7 +203,7 @@ import {
getTimeDifferenceHoursAndMinutes, getTimeDifferenceHoursAndMinutes,
getTimezoneOffset, getTimezoneOffset,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
guessTimezoneName, getBrowserTimezoneName,
isDateRangeMatchFullMonths, isDateRangeMatchFullMonths,
isDateRangeMatchFullYears, isDateRangeMatchFullYears,
isPM isPM
@@ -1159,20 +1160,20 @@ export function useI18n() {
return allRecentMonthDateRanges; return allRecentMonthDateRanges;
} }
function getAllTimezones(includeSystemDefault?: boolean): LocalizedTimezoneInfo[] { function getAllTimezones(unixTime: number, includeSystemDefault?: boolean): LocalizedTimezoneInfo[] {
const numeralSystem = getCurrentNumeralSystemType(); const numeralSystem = getCurrentNumeralSystemType();
const defaultTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getBrowserTimezoneOffset()); const defaultTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getBrowserTimezoneOffset(unixTime));
const defaultTimezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes(); const defaultTimezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes(unixTime);
const allTimezoneInfos: LocalizedTimezoneInfo[] = []; const allTimezoneInfos: LocalizedTimezoneInfo[] = [];
for (const timezoneInfo of ALL_TIMEZONES) { for (const timezoneInfo of ALL_TIMEZONES) {
const utcOffset = (timezoneInfo.timezoneName !== UTC_TIMEZONE.timezoneName ? numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(timezoneInfo.timezoneName)) : ''); const utcOffset = (timezoneInfo.timezoneName !== UTC_TIMEZONE.timezoneName ? numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(unixTime, timezoneInfo.timezoneName)) : '');
const displayName = t(`timezone.${timezoneInfo.displayName}`); const displayName = t(`timezone.${timezoneInfo.displayName}`);
allTimezoneInfos.push({ allTimezoneInfos.push({
name: timezoneInfo.timezoneName, name: timezoneInfo.timezoneName,
utcOffset: utcOffset, utcOffset: utcOffset,
utcOffsetMinutes: getTimezoneOffsetMinutes(timezoneInfo.timezoneName), utcOffsetMinutes: getTimezoneOffsetMinutes(unixTime, timezoneInfo.timezoneName),
displayName: displayName, displayName: displayName,
displayNameWithUtcOffset: `(UTC${utcOffset}) ${displayName}` displayNameWithUtcOffset: `(UTC${utcOffset}) ${displayName}`
}); });
@@ -1206,7 +1207,7 @@ export function useI18n() {
function getAllTimezoneTypesUsedForStatistics(currentTimezone?: string): TypeAndDisplayName[] { function getAllTimezoneTypesUsedForStatistics(currentTimezone?: string): TypeAndDisplayName[] {
const numeralSystem = getCurrentNumeralSystemType(); const numeralSystem = getCurrentNumeralSystemType();
const currentTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(currentTimezone)); const currentTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(getCurrentUnixTime(), currentTimezone));
return [ return [
{ {
@@ -1789,12 +1790,11 @@ export function useI18n() {
return formatGregorianCalendarMonthDashDay(monthDay, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType, numeralSystem: numeralSystem })); return formatGregorianCalendarMonthDashDay(monthDay, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType, numeralSystem: numeralSystem }));
} }
function formatUnixTimeToGregorianLikeYearQuarter(unixTime: number): string { function formatDateTimeToGregorianLikeYearQuarter(dateTime: DateTime): string {
const gregorianLikeCalendarType = getGregorianLikeCalendarType(); const gregorianLikeCalendarType = getGregorianLikeCalendarType();
const dateTimeFormatOptions = getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType }); const dateTimeFormatOptions = getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType });
const date = parseDateTimeFromUnixTime(unixTime); const year = dateTime.getLocalizedCalendarYear(dateTimeFormatOptions);
const year = date.getLocalizedCalendarYear(dateTimeFormatOptions); const quarter = dateTime.getLocalizedCalendarQuarter(dateTimeFormatOptions);
const quarter = date.getLocalizedCalendarQuarter(dateTimeFormatOptions);
return formatYearQuarter(year, quarter); return formatYearQuarter(year, quarter);
} }
@@ -1887,9 +1887,9 @@ export function useI18n() {
return `${displayStartTime} ~ ${displayEndTime}`; return `${displayStartTime} ~ ${displayEndTime}`;
} }
function getTimezoneDifferenceDisplayText(utcOffset: number): string { function getTimezoneDifferenceDisplayText(unixTime: number, utcOffset: number): string {
const numeralSystem = getCurrentNumeralSystemType(); const numeralSystem = getCurrentNumeralSystemType();
const defaultTimezoneOffset = getTimezoneOffsetMinutes(); const defaultTimezoneOffset = getTimezoneOffsetMinutes(unixTime);
const offsetTime = getTimeDifferenceHoursAndMinutes(utcOffset - defaultTimezoneOffset); const offsetTime = getTimeDifferenceHoursAndMinutes(utcOffset - defaultTimezoneOffset);
if (utcOffset > defaultTimezoneOffset) { if (utcOffset > defaultTimezoneOffset) {
@@ -2284,19 +2284,11 @@ export function useI18n() {
} }
function setTimeZone(timezone: string): void { function setTimeZone(timezone: string): void {
let timezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes();
if (timezone) { if (timezone) {
timezoneOffsetMinutes = getTimezoneOffsetMinutes(timezone); moment.tz.setDefault(timezone);
} else {
moment.tz.setDefault();
} }
moment.tz.add(moment.tz.pack({
name: 'Fixed/Timezone',
abbrs: ['FIX'],
offsets: [-timezoneOffsetMinutes],
untils: [0]
}));
moment.tz.setDefault('Fixed/Timezone');
} }
function initLocale(lastUserLanguage?: string, timezone?: string): LocaleDefaultSettings | null { function initLocale(lastUserLanguage?: string, timezone?: string): LocaleDefaultSettings | null {
@@ -2317,7 +2309,7 @@ export function useI18n() {
logger.info(`Current timezone is ${timezone}`); logger.info(`Current timezone is ${timezone}`);
setTimeZone(timezone); setTimeZone(timezone);
} else { } else {
logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${guessTimezoneName()})`); logger.info(`No timezone is set, use browser default ${getTimezoneOffset(getCurrentUnixTime())} (${getBrowserTimezoneName()})`);
setTimeZone(''); setTimeZone('');
} }
@@ -2423,36 +2415,36 @@ export function useI18n() {
isLongTimeMinuteTwoDigits, isLongTimeMinuteTwoDigits,
isLongTimeSecondTwoDigits, isLongTimeSecondTwoDigits,
// format date time (by calendar display type) functions // format date time (by calendar display type) functions
getCalendarDisplayShortYearFromUnixTime: (unixTime: number, numeralSystem?: NumeralSystem, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem }), utcOffset, currentUtcOffset), getCalendarDisplayShortYearFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
getCalendarDisplayShortMonthFromUnixTime: (unixTime: number, numeralSystem?: NumeralSystem, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMM', getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem }), utcOffset, currentUtcOffset), getCalendarDisplayShortMonthFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, 'MMM', getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
getCalendarDisplayDayOfMonthFromUnixTime: (unixTime: number, numeralSystem?: NumeralSystem, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDayFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem }), utcOffset, currentUtcOffset), getCalendarDisplayDayOfMonthFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, getLocalizedShortDayFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
// format date time (by date display type) functions // format date time (by date display type) functions
parseDateTimeFromLongDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat()), parseDateTimeFromLongDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat()),
parseDateTimeFromShortDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat()), parseDateTimeFromShortDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat()),
formatUnixTimeToLongDateTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToLongDateTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat(), getDateTimeFormatOptions()),
formatUnixTimeToShortDateTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToShortDateTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat(), getDateTimeFormatOptions()),
formatUnixTimeToLongDate: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongDateFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToLongDate: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongDateFormat(), getDateTimeFormatOptions()),
formatUnixTimeToShortDate: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDateFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToShortDate: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortDateFormat(), getDateTimeFormatOptions()),
formatUnixTimeToLongMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToLongMonthDay: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions()),
formatUnixTimeToShortMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortMonthDayFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToShortMonthDay: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortMonthDayFormat(), getDateTimeFormatOptions()),
formatUnixTimeToLongTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToLongTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongTimeFormat(), getDateTimeFormatOptions()),
formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset), formatDateTimeToShortTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortTimeFormat(), getDateTimeFormatOptions()),
formatGregorianTextualYearMonthDayToLongDate: (date: TextualYearMonthDay) => formatGregorianCalendarYearDashMonthDashDay(date, getLocalizedLongDateFormat(), getDateTimeFormatOptions()), formatGregorianTextualYearMonthDayToLongDate: (date: TextualYearMonthDay) => formatGregorianCalendarYearDashMonthDashDay(date, getLocalizedLongDateFormat(), getDateTimeFormatOptions()),
// format date time (Gregorian calendar and Gregorian-like calendar) functions // format date time (Gregorian calendar and Gregorian-like calendar) functions
formatUnixTimeToGregorianLikeLongYear: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeLongYear: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatUnixTimeToGregorianLikeShortYear: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeShortYear: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatUnixTimeToGregorianLikeLongYearMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeLongYearMonth: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatUnixTimeToGregorianLikeShortYearMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeShortYearMonth: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatUnixTimeToGregorianLikeLongMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeLongMonth: (dateTime: DateTime) => formatDateTime(dateTime, 'MMMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatUnixTimeToGregorianLikeShortMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset), formatDateTimeToGregorianLikeShortMonth: (dateTime: DateTime) => formatDateTime(dateTime, 'MMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatGregorianTextualMonthDayToGregorianLikeLongMonthDay, formatGregorianTextualMonthDayToGregorianLikeLongMonthDay,
formatUnixTimeToGregorianLikeYearQuarter, formatDateTimeToGregorianLikeYearQuarter,
formatYearQuarterToGregorianLikeYearQuarter, formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear, formatDateTimeToGregorianLikeFiscalYear: (dateTime: DateTime) => formatUnixTimeToGregorianLikeFiscalYear(dateTime.getUnixTime()),
formatGregorianYearToGregorianLikeFiscalYear, formatGregorianYearToGregorianLikeFiscalYear,
formatFiscalYearStartToGregorianLikeLongMonth, formatFiscalYearStartToGregorianLikeLongMonth,
// format date time (Gregorian calendar) functions // format date time (Gregorian calendar) functions
formatUnixTimeToGregorianDefaultDateTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, KnownDateTimeFormat.DefaultDateTime.format, getDateTimeFormatOptions({ numeralSystem: NumeralSystem.WesternArabicNumerals, calendarType: CalendarType.Gregorian }), utcOffset, currentUtcOffset), formatDateTimeToGregorianDefaultDateTime: (dateTime: DateTime) => formatDateTime(dateTime, KnownDateTimeFormat.DefaultDateTime.format, getDateTimeFormatOptions({ numeralSystem: NumeralSystem.WesternArabicNumerals, calendarType: CalendarType.Gregorian })),
// other format date time functions // other format date time functions
formatDateRange, formatDateRange,
getTimezoneDifferenceDisplayText, getTimezoneDifferenceDisplayText,
+18 -19
View File
@@ -226,11 +226,11 @@ export class Transaction implements TransactionInfoResponse {
this._displayDayOfWeek = displayDayOfWeek; this._displayDayOfWeek = displayDayOfWeek;
} }
public toCreateRequest(clientSessionId: string, actualTime?: number): TransactionCreateRequest { public toCreateRequest(clientSessionId: string): TransactionCreateRequest {
return { return {
type: this.type, type: this.type,
categoryId: this.getCategoryId(), categoryId: this.getCategoryId(),
time: actualTime ? actualTime : this.time, time: this.time,
utcOffset: this.utcOffset, utcOffset: this.utcOffset,
sourceAccountId: this.sourceAccountId, sourceAccountId: this.sourceAccountId,
destinationAccountId: this.type === TransactionType.Transfer ? this.destinationAccountId : '0', destinationAccountId: this.type === TransactionType.Transfer ? this.destinationAccountId : '0',
@@ -245,7 +245,7 @@ export class Transaction implements TransactionInfoResponse {
}; };
} }
public toModifyRequest(actualTime?: number): TransactionModifyRequest { public toModifyRequest(): TransactionModifyRequest {
let categoryId = this.getCategoryId(); let categoryId = this.getCategoryId();
if (this.type === TransactionType.ModifyBalance) { if (this.type === TransactionType.ModifyBalance) {
@@ -255,7 +255,7 @@ export class Transaction implements TransactionInfoResponse {
return { return {
id: this.id, id: this.id,
categoryId: categoryId, categoryId: categoryId,
time: actualTime ? actualTime : this.time, time: this.time,
utcOffset: this.utcOffset, utcOffset: this.utcOffset,
sourceAccountId: this.sourceAccountId, sourceAccountId: this.sourceAccountId,
destinationAccountId: this.type === TransactionType.Transfer ? this.destinationAccountId : '0', destinationAccountId: this.type === TransactionType.Transfer ? this.destinationAccountId : '0',
@@ -677,20 +677,20 @@ export const ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE = [
export type TransactionAmountsRequestType = typeof ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE[number]; export type TransactionAmountsRequestType = typeof ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE[number];
export const LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES: PartialRecord<TransactionAmountsRequestType, number> = { export const LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES: TransactionAmountsRequestType[] = [
'monthBeforeLast10Months': 11, 'monthBeforeLast10Months',
'monthBeforeLast9Months': 10, 'monthBeforeLast9Months',
'monthBeforeLast8Months': 9, 'monthBeforeLast8Months',
'monthBeforeLast7Months': 8, 'monthBeforeLast7Months',
'monthBeforeLast6Months': 7, 'monthBeforeLast6Months',
'monthBeforeLast5Months': 6, 'monthBeforeLast5Months',
'monthBeforeLast4Months': 5, 'monthBeforeLast4Months',
'monthBeforeLast3Months': 4, 'monthBeforeLast3Months',
'monthBeforeLast2Months': 3, 'monthBeforeLast2Months',
'monthBeforeLastMonth': 2, 'monthBeforeLastMonth',
'lastMonth': 1, 'lastMonth',
'thisMonth': 0 'thisMonth'
}; ];
export interface TransactionAmountsRequestParams extends PartialRecord<TransactionAmountsRequestType, StartEndTime> { export interface TransactionAmountsRequestParams extends PartialRecord<TransactionAmountsRequestType, StartEndTime> {
readonly useTransactionTimezone: boolean; readonly useTransactionTimezone: boolean;
@@ -1009,7 +1009,6 @@ export interface TransactionOverviewResponseItem {
export interface TransactionMonthlyIncomeAndExpenseData { export interface TransactionMonthlyIncomeAndExpenseData {
readonly monthStartTime: number; readonly monthStartTime: number;
readonly monthsBeforeCurrentMonth: number;
readonly incomeAmount: number; readonly incomeAmount: number;
readonly expenseAmount: number; readonly expenseAmount: number;
readonly incompleteIncomeAmount: boolean; readonly incompleteIncomeAmount: boolean;
+9 -17
View File
@@ -53,12 +53,7 @@ import {
splitItemsToMap, splitItemsToMap,
countSplitItems countSplitItems
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import { parseDateTimeFromUnixTimeWithTimezoneOffset } from '@/lib/datetime.ts';
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
parseDateTimeFromUnixTime
} from '@/lib/datetime.ts';
import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts'; import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts';
import { getCurrencyFraction } from '@/lib/currency.ts'; import { getCurrencyFraction } from '@/lib/currency.ts';
import { getFirstVisibleCategoryId } from '@/lib/category.ts'; import { getFirstVisibleCategoryId } from '@/lib/category.ts';
@@ -185,14 +180,13 @@ export const useTransactionsStore = defineStore('transactions', () => {
} }
if (transactionPageWrapper.items && transactionPageWrapper.items.length) { if (transactionPageWrapper.items && transactionPageWrapper.items.length) {
const currentUtcOffset = getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone);
let currentMonthListIndex = -1; let currentMonthListIndex = -1;
let currentMonthList: TransactionMonthList | null = null; let currentMonthList: TransactionMonthList | null = null;
for (const [item, index] of itemAndIndex(transactionPageWrapper.items)) { for (const [item, index] of itemAndIndex(transactionPageWrapper.items)) {
fillTransactionObject(item, currentUtcOffset); fillTransactionObject(item);
const transactionTime = parseDateTimeFromUnixTime(item.time, item.utcOffset, currentUtcOffset); const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(item.time, item.utcOffset);
const transactionYear = transactionTime.getGregorianCalendarYear(); const transactionYear = transactionTime.getGregorianCalendarYear();
const transactionMonth = transactionTime.getGregorianCalendarMonth(); const transactionMonth = transactionTime.getGregorianCalendarMonth();
const transactionYearDashMonth = transactionTime.getGregorianCalendarYearDashMonth(); const transactionYearDashMonth = transactionTime.getGregorianCalendarYearDashMonth();
@@ -264,8 +258,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
} }
function updateTransactionInTransactionList({ currentTransaction, defaultCurrency }: { currentTransaction: Transaction, defaultCurrency: string }): void { function updateTransactionInTransactionList({ currentTransaction, defaultCurrency }: { currentTransaction: Transaction, defaultCurrency: string }): void {
const currentUtcOffset = getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone); const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(currentTransaction.time, currentTransaction.utcOffset);
const transactionTime = parseDateTimeFromUnixTime(currentTransaction.time, currentTransaction.utcOffset, currentUtcOffset);
const transactionYear = transactionTime.getGregorianCalendarYear(); const transactionYear = transactionTime.getGregorianCalendarYear();
const transactionMonth = transactionTime.getGregorianCalendarMonth(); const transactionMonth = transactionTime.getGregorianCalendarMonth();
@@ -276,7 +269,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
for (const [transaction, transactionIndex] of itemAndIndex(transactionMonthList.items)) { for (const [transaction, transactionIndex] of itemAndIndex(transactionMonthList.items)) {
if (transaction.id === currentTransaction.id) { if (transaction.id === currentTransaction.id) {
fillTransactionObject(currentTransaction, currentUtcOffset); fillTransactionObject(currentTransaction);
if (transactionYear !== transactionMonthList.year || if (transactionYear !== transactionMonthList.year ||
transactionMonth !== transactionMonthList.month || transactionMonth !== transactionMonthList.month ||
@@ -445,12 +438,12 @@ export const useTransactionsStore = defineStore('transactions', () => {
} }
} }
function fillTransactionObject(transaction: Transaction, currentUtcOffset: number): void { function fillTransactionObject(transaction: Transaction): void {
if (!transaction) { if (!transaction) {
return; return;
} }
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset); const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
transaction.setDisplayDate(transactionTime.getGregorianCalendarYearDashMonthDashDay(), transactionTime.getGregorianCalendarDay(), transactionTime.getWeekDay()); transaction.setDisplayDate(transactionTime.getGregorianCalendarYearDashMonthDashDay(), transactionTime.getGregorianCalendarDay(), transactionTime.getWeekDay());
if (transaction.sourceAccountId) { if (transaction.sourceAccountId) {
@@ -1026,7 +1019,6 @@ export const useTransactionsStore = defineStore('transactions', () => {
function saveTransaction({ transaction, defaultCurrency, isEdit, clientSessionId }: { transaction: Transaction, defaultCurrency: string, isEdit: boolean, clientSessionId: string }): Promise<Transaction> { function saveTransaction({ transaction, defaultCurrency, isEdit, clientSessionId }: { transaction: Transaction, defaultCurrency: string, isEdit: boolean, clientSessionId: string }): Promise<Transaction> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const actualTime = getActualUnixTimeForStore(transaction.time, transaction.utcOffset, getBrowserTimezoneOffsetMinutes());
let promise: ApiResponsePromise<TransactionInfoResponse>; let promise: ApiResponsePromise<TransactionInfoResponse>;
if (transaction.type !== TransactionType.Expense && if (transaction.type !== TransactionType.Expense &&
@@ -1041,9 +1033,9 @@ export const useTransactionsStore = defineStore('transactions', () => {
} }
if (!isEdit) { if (!isEdit) {
promise = services.addTransaction(transaction.toCreateRequest(clientSessionId, actualTime)); promise = services.addTransaction(transaction.toCreateRequest(clientSessionId));
} else { } else {
promise = services.modifyTransaction(transaction.toModifyRequest(actualTime)); promise = services.modifyTransaction(transaction.toModifyRequest());
} }
promise.then(response => { promise.then(response => {
+4 -2
View File
@@ -9,6 +9,7 @@ import type { VersionInfo } from '@/core/version.ts';
import type { LatestExchangeRateResponse } from '@/models/exchange_rate.ts'; import type { LatestExchangeRateResponse } from '@/models/exchange_rate.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { getMapProvider } from '@/lib/server_settings.ts'; import { getMapProvider } from '@/lib/server_settings.ts';
import { getMapWebsite } from '@/lib/map/index.ts'; import { getMapWebsite } from '@/lib/map/index.ts';
import { getLicense, getThirdPartyLicenses } from '@/lib/licenses.ts'; import { getLicense, getThirdPartyLicenses } from '@/lib/licenses.ts';
@@ -16,7 +17,7 @@ import { formatDisplayVersion, getClientDisplayVersion, getClientBuildTime } fro
import { clearBrowserCaches } from '@/lib/ui/common.ts'; import { clearBrowserCaches } from '@/lib/ui/common.ts';
export function useAboutPageBase() { export function useAboutPageBase() {
const { tt, formatUnixTimeToLongDateTime } = useI18n(); const { tt, formatDateTimeToLongDateTime } = useI18n();
const systemsStore = useSystemsStore(); const systemsStore = useSystemsStore();
const exchangeRatesStore = useExchangeRatesStore(); const exchangeRatesStore = useExchangeRatesStore();
@@ -41,7 +42,8 @@ export function useAboutPageBase() {
return time; return time;
} }
return formatUnixTimeToLongDateTime(parseInt(time)); const buildDateTime = parseDateTimeFromUnixTime(parseInt(time));
return formatDateTimeToLongDateTime(buildDateTime);
}); });
const exchangeRatesData = computed<LatestExchangeRateResponse | undefined>(() => exchangeRatesStore.latestExchangeRates.data); const exchangeRatesData = computed<LatestExchangeRateResponse | undefined>(() => exchangeRatesStore.latestExchangeRates.data);
+8 -3
View File
@@ -12,9 +12,10 @@ import type {
} from '@/models/exchange_rate.ts'; } from '@/models/exchange_rate.ts';
import { getExchangedAmountByRate } from '@/lib/numeral.ts'; import { getExchangedAmountByRate } from '@/lib/numeral.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
export function useExchangeRatesPageBase() { export function useExchangeRatesPageBase() {
const { getAllDisplayExchangeRates, formatUnixTimeToLongDate, parseAmountFromWesternArabicNumerals } = useI18n(); const { getAllDisplayExchangeRates, formatDateTimeToLongDate, parseAmountFromWesternArabicNumerals } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore(); const exchangeRatesStore = useExchangeRatesStore();
@@ -27,8 +28,12 @@ export function useExchangeRatesPageBase() {
const isUserCustomExchangeRates = computed<boolean>(() => exchangeRatesStore.isUserCustomExchangeRates); const isUserCustomExchangeRates = computed<boolean>(() => exchangeRatesStore.isUserCustomExchangeRates);
const exchangeRatesDataUpdateTime = computed<string>(() => { const exchangeRatesDataUpdateTime = computed<string>(() => {
const exchangeRatesLastUpdateTime = exchangeRatesStore.exchangeRatesLastUpdateTime; if (!exchangeRatesStore.exchangeRatesLastUpdateTime) {
return exchangeRatesLastUpdateTime ? formatUnixTimeToLongDate(exchangeRatesLastUpdateTime) : ''; return '';
}
const exchangeRatesLastUpdateTime = parseDateTimeFromUnixTime(exchangeRatesStore.exchangeRatesLastUpdateTime);
return formatDateTimeToLongDate(exchangeRatesLastUpdateTime);
}); });
const availableExchangeRates = computed<LocalizedLatestExchangeRate[]>(() => { const availableExchangeRates = computed<LocalizedLatestExchangeRate[]>(() => {
+13 -11
View File
@@ -17,12 +17,14 @@ import type {
TransactionOverviewResponseItem TransactionOverviewResponseItem
} from '@/models/transaction.ts'; } from '@/models/transaction.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
export function useHomePageBase() { export function useHomePageBase() {
const { const {
formatUnixTimeToLongDate, formatDateTimeToLongDate,
formatUnixTimeToLongMonthDay, formatDateTimeToLongMonthDay,
formatUnixTimeToGregorianLikeLongYear, formatDateTimeToGregorianLikeLongYear,
formatUnixTimeToGregorianLikeLongMonth, formatDateTimeToGregorianLikeLongMonth,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -57,19 +59,19 @@ export function useHomePageBase() {
const displayDateRange = computed<TransactionOverviewDisplayTime>(() => { const displayDateRange = computed<TransactionOverviewDisplayTime>(() => {
return { return {
today: { today: {
displayTime: formatUnixTimeToLongDate(overviewStore.transactionDataRange.today.startTime), displayTime: formatDateTimeToLongDate(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.today.startTime)),
}, },
thisWeek: { thisWeek: {
startTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisWeek.startTime), startTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisWeek.startTime)),
endTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisWeek.endTime) endTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisWeek.endTime))
}, },
thisMonth: { thisMonth: {
displayTime: formatUnixTimeToGregorianLikeLongMonth(overviewStore.transactionDataRange.thisMonth.startTime), displayTime: formatDateTimeToGregorianLikeLongMonth(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.startTime)),
startTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisMonth.startTime), startTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.startTime)),
endTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisMonth.endTime) endTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.endTime))
}, },
thisYear: { thisYear: {
displayTime: formatUnixTimeToGregorianLikeLongYear(overviewStore.transactionDataRange.thisYear.startTime) displayTime: formatDateTimeToGregorianLikeLongYear(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisYear.startTime))
} }
}; };
}); });
+42 -4
View File
@@ -9,7 +9,13 @@ import { AccountCategory, AccountType } from '@/core/account.ts';
import type { LocalizedAccountCategory } from '@/core/account.ts'; import type { LocalizedAccountCategory } from '@/core/account.ts';
import { Account } from '@/models/account.ts'; import { Account } from '@/models/account.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts'; import { isDefined } from '@/lib/common.ts';
import {
getTimezoneOffsetMinutes,
getSameDateTimeWithCurrentTimezone,
parseDateTimeFromUnixTimeWithBrowserTimezone,
getCurrentUnixTime
} from '@/lib/datetime.ts';
export interface DayAndDisplayName { export interface DayAndDisplayName {
readonly day: number; readonly day: number;
@@ -25,7 +31,7 @@ export function useAccountEditPageBase() {
const clientSessionId = ref<string>(''); const clientSessionId = ref<string>('');
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const submitting = ref<boolean>(false); const submitting = ref<boolean>(false);
const account = ref<Account>(Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime())); const account = ref<Account>(Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount()));
const subAccounts = ref<Account[]>([]); const subAccounts = ref<Account[]>([]);
const title = computed<string>(() => { const title = computed<string>(() => {
@@ -89,6 +95,18 @@ export function useAccountEditPageBase() {
const isAccountSupportCreditCardStatementDate = computed<boolean>(() => account.value && account.value.category === AccountCategory.CreditCard.type); const isAccountSupportCreditCardStatementDate = computed<boolean>(() => account.value && account.value.category === AccountCategory.CreditCard.type);
function getCurrentUnixTimeForNewAccount(): number {
return getSameDateTimeWithCurrentTimezone(parseDateTimeFromUnixTimeWithBrowserTimezone(getCurrentUnixTime())).getUnixTime();
}
function getDefaultTimezoneOffsetMinutes(account: Account): number {
if (!account.balanceTime) {
return 0;
}
return getTimezoneOffsetMinutes(account.balanceTime);
}
function getAccountCreditCardStatementDate(statementDate?: number): string | null { function getAccountCreditCardStatementDate(statementDate?: number): string | null {
for (const item of allAvailableMonthDays.value) { for (const item of allAvailableMonthDays.value) {
if (item.day === statementDate) { if (item.day === statementDate) {
@@ -99,6 +117,23 @@ export function useAccountEditPageBase() {
return null; return null;
} }
function updateAccountBalanceTime(account: Account, balanceTime: number): void {
if (!isDefined(account.balanceTime)) {
account.balanceTime = balanceTime;
return;
}
const oldUtcOffset = getTimezoneOffsetMinutes(account.balanceTime);
const newUtcOffset = getTimezoneOffsetMinutes(balanceTime);
if (oldUtcOffset === newUtcOffset) {
account.balanceTime = balanceTime;
return;
}
account.balanceTime = balanceTime - (newUtcOffset - oldUtcOffset) * 60;
}
function getInputEmptyProblemMessage(account: Account, isSubAccount: boolean): string | null { function getInputEmptyProblemMessage(account: Account, isSubAccount: boolean): string | null {
if (!isSubAccount && !account.category) { if (!isSubAccount && !account.category) {
return 'Account category cannot be blank'; return 'Account category cannot be blank';
@@ -122,7 +157,7 @@ export function useAccountEditPageBase() {
return false; return false;
} }
const subAccount = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime()); const subAccount = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
subAccounts.value.push(subAccount); subAccounts.value.push(subAccount);
return true; return true;
} }
@@ -133,7 +168,7 @@ export function useAccountEditPageBase() {
if (newAccount.subAccounts && newAccount.subAccounts.length > 0) { if (newAccount.subAccounts && newAccount.subAccounts.length > 0) {
for (const oldSubAccount of newAccount.subAccounts) { for (const oldSubAccount of newAccount.subAccounts) {
const subAccount: Account = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime()); const subAccount: Account = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
subAccount.fillFrom(oldSubAccount); subAccount.fillFrom(oldSubAccount);
subAccounts.value.push(subAccount); subAccounts.value.push(subAccount);
@@ -163,7 +198,10 @@ export function useAccountEditPageBase() {
allAvailableMonthDays, allAvailableMonthDays,
isAccountSupportCreditCardStatementDate, isAccountSupportCreditCardStatementDate,
// functions // functions
getCurrentUnixTimeForNewAccount,
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate, getAccountCreditCardStatementDate,
updateAccountBalanceTime,
isNewAccount, isNewAccount,
addSubAccount, addSubAccount,
setAccount setAccount
@@ -2,7 +2,6 @@ import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { useUserStore } from '@/stores/user.ts'; import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts'; import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
@@ -25,7 +24,8 @@ import { replaceAll } from '@/lib/common.ts';
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
export function useReconciliationStatementPageBase() { export function useReconciliationStatementPageBase() {
@@ -33,15 +33,14 @@ export function useReconciliationStatementPageBase() {
tt, tt,
getAllAccountBalanceTrendChartTypes, getAllAccountBalanceTrendChartTypes,
getAllStatisticsDateAggregationTypesWithShortName, getAllStatisticsDateAggregationTypesWithShortName,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatUnixTimeToLongDate, formatDateTimeToLongDate,
formatUnixTimeToShortTime, formatDateTimeToShortTime,
formatUnixTimeToGregorianDefaultDateTime, formatDateTimeToGregorianDefaultDateTime,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping, formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
const settingsStore = useSettingsStore();
const userStore = useUserStore(); const userStore = useUserStore();
const accountsStore = useAccountsStore(); const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore(); const transactionCategoriesStore = useTransactionCategoriesStore();
@@ -53,7 +52,6 @@ export function useReconciliationStatementPageBase() {
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek); const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart); const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency); const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllAccountBalanceTrendChartTypes()); const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllAccountBalanceTrendChartTypes());
@@ -79,11 +77,13 @@ export function useReconciliationStatementPageBase() {
const allCategoriesMap = computed<Record<string, TransactionCategory>>(() => transactionCategoriesStore.allTransactionCategoriesMap); const allCategoriesMap = computed<Record<string, TransactionCategory>>(() => transactionCategoriesStore.allTransactionCategoriesMap);
const displayStartDateTime = computed<string>(() => { const displayStartDateTime = computed<string>(() => {
return formatUnixTimeToLongDateTime(startTime.value); const dateTime = parseDateTimeFromUnixTime(startTime.value);
return formatDateTimeToLongDateTime(dateTime);
}); });
const displayEndDateTime = computed<string>(() => { const displayEndDateTime = computed<string>(() => {
return formatUnixTimeToLongDateTime(endTime.value); const dateTime = parseDateTimeFromUnixTime(endTime.value);
return formatDateTimeToLongDateTime(dateTime);
}); });
const displayTotalInflows = computed<string>(() => { const displayTotalInflows = computed<string>(() => {
@@ -160,15 +160,22 @@ export function useReconciliationStatementPageBase() {
} }
function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
return formatUnixTimeToLongDateTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDateTime(dateTime);
} }
function getDisplayDate(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { function getDisplayDate(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
return formatUnixTimeToLongDate(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDate(dateTime);
} }
function getDisplayTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { function getDisplayTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
return formatUnixTimeToShortTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToShortTime(dateTime);
}
function isSameAsDefaultTimezoneOffsetMinutes(transaction: TransactionReconciliationStatementResponseItemWithInfo): boolean {
return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time);
} }
function getDisplayTimezone(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { function getDisplayTimezone(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
@@ -227,7 +234,7 @@ export function useReconciliationStatementPageBase() {
const transactions = reconciliationStatements.value?.transactions ?? []; const transactions = reconciliationStatements.value?.transactions ?? [];
const rows = transactions.map(transaction => { const rows = transactions.map(transaction => {
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value).getUnixTime(); const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
const type = getDisplayTransactionType(transaction); const type = getDisplayTransactionType(transaction);
let categoryName = transaction.categoryName; let categoryName = transaction.categoryName;
let displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.sourceAmount); let displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.sourceAmount);
@@ -260,7 +267,7 @@ export function useReconciliationStatementPageBase() {
} }
return [ return [
formatUnixTimeToGregorianDefaultDateTime(transactionTime), formatDateTimeToGregorianDefaultDateTime(transactionTime),
type, type,
categoryName, categoryName,
displayAmount, displayAmount,
@@ -282,7 +289,6 @@ export function useReconciliationStatementPageBase() {
// computed states // computed states
firstDayOfWeek, firstDayOfWeek,
fiscalYearStart, fiscalYearStart,
currentTimezoneOffsetMinutes,
defaultCurrency, defaultCurrency,
allChartTypes, allChartTypes,
allDateAggregationTypes, allDateAggregationTypes,
@@ -303,6 +309,7 @@ export function useReconciliationStatementPageBase() {
getDisplayDateTime, getDisplayDateTime,
getDisplayDate, getDisplayDate,
getDisplayTime, getDisplayTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone, getDisplayTimezone,
getDisplaySourceAmount, getDisplaySourceAmount,
getDisplayDestinationAmount, getDisplayDestinationAmount,
@@ -15,6 +15,7 @@ import { CategoryType } from '@/core/category.ts';
import type { Account } from '@/models/account.ts'; import type { Account } from '@/models/account.ts';
import { isObjectEmpty } from '@/lib/common.ts'; import { isObjectEmpty } from '@/lib/common.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts';
export function useAppSettingPageBase() { export function useAppSettingPageBase() {
const { tt, getAllTimezones, getAllTimezoneTypesUsedForStatistics, getAllCurrencySortingTypes, setTimeZone } = useI18n(); const { tt, getAllTimezones, getAllTimezoneTypesUsedForStatistics, getAllCurrencySortingTypes, setTimeZone } = useI18n();
@@ -37,7 +38,7 @@ export function useAppSettingPageBase() {
]; ];
}); });
const allTimezones = computed<LocalizedTimezoneInfo[]>(() => getAllTimezones(true)); const allTimezones = computed<LocalizedTimezoneInfo[]>(() => getAllTimezones(getCurrentUnixTime(), true));
const allTimezoneTypesUsedForStatistics = computed<TypeAndDisplayName[]>(() => getAllTimezoneTypesUsedForStatistics()); const allTimezoneTypesUsedForStatistics = computed<TypeAndDisplayName[]>(() => getAllTimezoneTypesUsedForStatistics());
const allCurrencySortingTypes = computed<TypeAndDisplayName[]>(() => getAllCurrencySortingTypes()); const allCurrencySortingTypes = computed<TypeAndDisplayName[]>(() => getAllCurrencySortingTypes());
@@ -28,7 +28,11 @@ import type {
} from '@/models/transaction.ts'; } from '@/models/transaction.ts';
import { limitText, findNameByType, findDisplayNameByType } from '@/lib/common.ts'; import { limitText, findNameByType, findDisplayNameByType } from '@/lib/common.ts';
import { getYearMonthFirstUnixTime, getYearMonthLastUnixTime } from '@/lib/datetime.ts'; import {
parseDateTimeFromUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime
} from '@/lib/datetime.ts';
import { getDisplayColor, getCategoryDisplayColor, getAccountDisplayColor } from '@/lib/color.ts'; import { getDisplayColor, getCategoryDisplayColor, getAccountDisplayColor } from '@/lib/color.ts';
export function useStatisticsTransactionPageBase() { export function useStatisticsTransactionPageBase() {
@@ -37,8 +41,8 @@ export function useStatisticsTransactionPageBase() {
getAllDateRanges, getAllDateRanges,
getAllStatisticsSortingTypes, getAllStatisticsSortingTypes,
getAllStatisticsDateAggregationTypes, getAllStatisticsDateAggregationTypes,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatUnixTimeToGregorianLikeLongYearMonth, formatDateTimeToGregorianLikeLongYearMonth,
formatDateRange, formatDateRange,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -88,11 +92,11 @@ export function useStatisticsTransactionPageBase() {
const queryStartTime = computed<string>(() => { const queryStartTime = computed<string>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) { if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return formatUnixTimeToLongDateTime(query.value.categoricalChartStartTime); return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.categoricalChartStartTime));
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) { } else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(query.value.trendChartStartYearMonth)); return formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthFirstUnixTime(query.value.trendChartStartYearMonth)));
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) { } else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartStartTime); return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.assetTrendsChartStartTime));
} else { } else {
return ''; return '';
} }
@@ -100,11 +104,11 @@ export function useStatisticsTransactionPageBase() {
const queryEndTime = computed<string>(() => { const queryEndTime = computed<string>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) { if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return formatUnixTimeToLongDateTime(query.value.categoricalChartEndTime); return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.categoricalChartEndTime));
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) { } else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthLastUnixTime(query.value.trendChartEndYearMonth)); return formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthLastUnixTime(query.value.trendChartEndYearMonth)));
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) { } else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartEndTime); return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.assetTrendsChartEndTime));
} else { } else {
return ''; return '';
} }
@@ -36,6 +36,8 @@ import {
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
getSameDateTimeWithCurrentTimezone,
parseDateTimeFromUnixTimeWithBrowserTimezone,
getCurrentUnixTime getCurrentUnixTime
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
@@ -92,14 +94,14 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
const transaction = ref<Transaction | TransactionTemplate>(createNewTransactionModel(transactionDefaultType)); const transaction = ref<Transaction | TransactionTemplate>(createNewTransactionModel(transactionDefaultType));
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType()); const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone)); const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(transaction.value.time));
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance); const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency); const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const defaultAccountId = computed<string>(() => userStore.currentUserDefaultAccountId); const defaultAccountId = computed<string>(() => userStore.currentUserDefaultAccountId);
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek); const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType); const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType);
const allTimezones = computed<LocalizedTimezoneInfo[]>(() => getAllTimezones(true)); const allTimezones = computed<LocalizedTimezoneInfo[]>(() => getAllTimezones(transaction.value.time, true));
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts); const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts); const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allAccountsMap = computed<Record<string, Account>>(() => accountsStore.allAccountsMap); const allAccountsMap = computed<Record<string, Account>>(() => accountsStore.allAccountsMap);
@@ -274,7 +276,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
}); });
const transactionTimezoneTimeDifference = computed<string>(() => { const transactionTimezoneTimeDifference = computed<string>(() => {
return getTimezoneDifferenceDisplayText(transaction.value.utcOffset); return getTimezoneDifferenceDisplayText(transaction.value.time, transaction.value.utcOffset);
}); });
const geoLocationStatusInfo = computed<string>(() => { const geoLocationStatusInfo = computed<string>(() => {
@@ -331,8 +333,12 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
return !!inputEmptyProblemMessage.value; return !!inputEmptyProblemMessage.value;
}); });
function getCurrentUnixTimeForNewTransaction(): number {
return getSameDateTimeWithCurrentTimezone(parseDateTimeFromUnixTimeWithBrowserTimezone(getCurrentUnixTime())).getUnixTime();
}
function createNewTransactionModel(transactionType?: number): Transaction | TransactionTemplate { function createNewTransactionModel(transactionType?: number): Transaction | TransactionTemplate {
const now: number = getCurrentUnixTime(); const now: number = getCurrentUnixTimeForNewTransaction();
const currentTimezone: string = settingsStore.appSettings.timeZone; const currentTimezone: string = settingsStore.appSettings.timeZone;
let defaultType: TransactionType = TransactionType.Expense; let defaultType: TransactionType = TransactionType.Expense;
@@ -343,7 +349,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
defaultType = TransactionType.Transfer; defaultType = TransactionType.Transfer;
} }
let newTransaction: Transaction | TransactionTemplate = Transaction.createNewTransaction(defaultType, now, currentTimezone, getTimezoneOffsetMinutes(currentTimezone)); let newTransaction: Transaction | TransactionTemplate = Transaction.createNewTransaction(defaultType, now, currentTimezone, getTimezoneOffsetMinutes(now, currentTimezone));
if (type === TransactionEditPageType.Template) { if (type === TransactionEditPageType.Template) {
newTransaction = TransactionTemplate.createNewTransactionTemplate(newTransaction); newTransaction = TransactionTemplate.createNewTransactionTemplate(newTransaction);
@@ -352,6 +358,25 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
return newTransaction; return newTransaction;
} }
function updateTransactionTime(newTime: number): void {
transaction.value.time = newTime;
updateTransactionTimezone(transaction.value.timeZone ?? '');
}
function updateTransactionTimezone(timezoneName: string): void {
const oldUtcOffset = transaction.value.utcOffset;
for (const timezone of allTimezones.value) {
if (timezone.name === timezoneName) {
transaction.value.timeZone = timezone.name;
transaction.value.utcOffset = timezone.utcOffsetMinutes;
break;
}
}
transaction.value.time = transaction.value.time - (transaction.value.utcOffset - oldUtcOffset) * 60;
}
function swapTransactionData(swapAccount: boolean, swapAmount: boolean): void { function swapTransactionData(swapAccount: boolean, swapAmount: boolean): void {
if (swapAccount) { if (swapAccount) {
const oldSourceAccountId = transaction.value.sourceAccountId; const oldSourceAccountId = transaction.value.sourceAccountId;
@@ -396,15 +421,6 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
} }
}); });
watch(() => transaction.value.timeZone, (newValue) => {
for (const timezone of allTimezones.value) {
if (timezone.name === newValue) {
transaction.value.utcOffset = timezone.utcOffsetMinutes;
break;
}
}
});
return { return {
// constants // constants
isSupportGeoLocation, isSupportGeoLocation,
@@ -460,6 +476,8 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
inputIsEmpty, inputIsEmpty,
// functions // functions
createNewTransactionModel, createNewTransactionModel,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData, swapTransactionData,
getDisplayAmount, getDisplayAmount,
getTransactionPictureUrl getTransactionPictureUrl
@@ -23,13 +23,12 @@ import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts'
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime, getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage, getSameDateTimeWithBrowserTimezone,
getCurrentDateTime, getCurrentDateTime,
parseDateTimeFromUnixTime, parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
isDateRangeMatchOneMonth isDateRangeMatchOneMonth
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
@@ -76,10 +75,10 @@ export function useTransactionListPageBase() {
tt, tt,
getAllDateRanges, getAllDateRanges,
getCurrentNumeralSystemType, getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatUnixTimeToLongDate, formatDateTimeToLongDate,
formatUnixTimeToShortTime, formatDateTimeToShortTime,
formatUnixTimeToGregorianLikeLongYearMonth, formatDateTimeToGregorianLikeLongYearMonth,
formatDateRange, formatDateRange,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -98,7 +97,6 @@ export function useTransactionListPageBase() {
const currentCalendarDate = ref<TextualYearMonthDay | ''>(''); const currentCalendarDate = ref<TextualYearMonthDay | ''>('');
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType()); const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek); const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart); const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
const defaultCurrency = computed<string>(() => getUnifiedSelectedAccountsCurrencyOrDefaultCurrency(allAccountsMap.value, queryAllFilterAccountIds.value, userStore.currentUserDefaultCurrency)); const defaultCurrency = computed<string>(() => getUnifiedSelectedAccountsCurrencyOrDefaultCurrency(allAccountsMap.value, queryAllFilterAccountIds.value, userStore.currentUserDefaultCurrency));
@@ -161,8 +159,8 @@ export function useTransactionListPageBase() {
return formatDateRange(query.value.dateType, query.value.minTime, query.value.maxTime); return formatDateRange(query.value.dateType, query.value.minTime, query.value.maxTime);
}); });
const queryMinTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.minTime)); const queryMinTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.minTime)));
const queryMaxTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.maxTime)); const queryMaxTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.maxTime)));
const queryMonthlyData = computed<boolean>(() => isDateRangeMatchOneMonth(query.value.minTime, query.value.maxTime)); const queryMonthlyData = computed<boolean>(() => isDateRangeMatchOneMonth(query.value.minTime, query.value.maxTime));
const queryMonth = computed<Year0BasedMonth>(() => { const queryMonth = computed<Year0BasedMonth>(() => {
if (!query.value.minTime || !query.value.maxTime) { if (!query.value.minTime || !query.value.maxTime) {
@@ -235,8 +233,8 @@ export function useTransactionListPageBase() {
return displayAmount.join(' ~ '); return displayAmount.join(' ~ ');
}); });
const transactionCalendarMinDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(query.value.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))); const transactionCalendarMinDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(query.value.minTime)).getUnixTime()));
const transactionCalendarMaxDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(query.value.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))); const transactionCalendarMaxDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(query.value.maxTime)).getUnixTime()));
const currentMonthTransactionData = computed<TransactionMonthList | null>(() => { const currentMonthTransactionData = computed<TransactionMonthList | null>(() => {
const allTransactions = transactionsStore.transactions; const allTransactions = transactionsStore.transactions;
@@ -284,6 +282,10 @@ export function useTransactionListPageBase() {
return false; return false;
} }
function isSameAsDefaultTimezoneOffsetMinutes(transaction: Transaction): boolean {
return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time);
}
function formatAmount(amount: number, hideAmount: boolean, currencyCode: string): string { function formatAmount(amount: number, hideAmount: boolean, currencyCode: string): string {
if (hideAmount) { if (hideAmount) {
return formatAmountToLocalizedNumeralsWithCurrency(DISPLAY_HIDDEN_AMOUNT, currencyCode); return formatAmountToLocalizedNumeralsWithCurrency(DISPLAY_HIDDEN_AMOUNT, currencyCode);
@@ -293,15 +295,18 @@ export function useTransactionListPageBase() {
} }
function getDisplayTime(transaction: Transaction): string { function getDisplayTime(transaction: Transaction): string {
return formatUnixTimeToShortTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToShortTime(dateTime);
} }
function getDisplayLongDate(transaction: Transaction): string { function getDisplayLongDate(transaction: Transaction): string {
return formatUnixTimeToLongDate(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDate(dateTime);
} }
function getDisplayLongYearMonth(transactionMonthList: TransactionMonthList): string { function getDisplayLongYearMonth(transactionMonthList: TransactionMonthList): string {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(transactionMonthList.yearDashMonth)); const yearMonthDateTime = parseDateTimeFromUnixTime(getYearMonthFirstUnixTime(transactionMonthList.yearDashMonth));
return formatDateTimeToGregorianLikeLongYearMonth(yearMonthDateTime);
} }
function getDisplayTimezone(transaction: Transaction): string { function getDisplayTimezone(transaction: Transaction): string {
@@ -310,8 +315,10 @@ export function useTransactionListPageBase() {
} }
function getDisplayTimeInDefaultTimezone(transaction: Transaction): string { function getDisplayTimeInDefaultTimezone(transaction: Transaction): string {
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(settingsStore.appSettings.timeZone)); const timezoneOffsetMinutes = getTimezoneOffsetMinutes(transaction.time);
return `${formatUnixTimeToLongDateTime(transaction.time)} (UTC${utcOffset})`; const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, timezoneOffsetMinutes);
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getUtcOffsetByUtcOffsetMinutes(timezoneOffsetMinutes));
return `${formatDateTimeToLongDateTime(dateTime)} (UTC${utcOffset})`;
} }
function getDisplayAmount(transaction: Transaction): string { function getDisplayAmount(transaction: Transaction): string {
@@ -372,7 +379,6 @@ export function useTransactionListPageBase() {
customMaxDatetime, customMaxDatetime,
currentCalendarDate, currentCalendarDate,
// computed states // computed states
currentTimezoneOffsetMinutes,
firstDayOfWeek, firstDayOfWeek,
fiscalYearStart, fiscalYearStart,
defaultCurrency, defaultCurrency,
@@ -410,6 +416,7 @@ export function useTransactionListPageBase() {
canAddTransaction, canAddTransaction,
// functions // functions
hasSubCategoryInQuery, hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTime, getDisplayTime,
getDisplayLongDate, getDisplayLongDate,
getDisplayLongYearMonth, getDisplayLongYearMonth,
+1 -5
View File
@@ -202,12 +202,10 @@ import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useOverviewStore } from '@/stores/overview.ts'; import { useOverviewStore } from '@/stores/overview.ts';
import { entries } from '@/core/base.ts';
import { type NumeralSystem } from '@/core/numeral.ts'; import { type NumeralSystem } from '@/core/numeral.ts';
import { DateRange } from '@/core/datetime.ts'; import { DateRange } from '@/core/datetime.ts';
import { ThemeType } from '@/core/theme.ts'; import { ThemeType } from '@/core/theme.ts';
import { import {
type TransactionAmountsRequestType,
type TransactionMonthlyIncomeAndExpenseData, type TransactionMonthlyIncomeAndExpenseData,
LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES
} from '@/models/transaction.ts'; } from '@/models/transaction.ts';
@@ -280,8 +278,7 @@ const monthlyIncomeAndExpenseData = computed<TransactionMonthlyIncomeAndExpenseD
return data; return data;
} }
for (const [type, monthDiff] of entries(LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES)) { for (const amountRequestType of LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES) {
const amountRequestType = type as TransactionAmountsRequestType;
const dateRange = overviewStore.transactionDataRange[amountRequestType]; const dateRange = overviewStore.transactionDataRange[amountRequestType];
if (!dateRange) { if (!dateRange) {
@@ -292,7 +289,6 @@ const monthlyIncomeAndExpenseData = computed<TransactionMonthlyIncomeAndExpenseD
data.push({ data.push({
monthStartTime: dateRange.startTime, monthStartTime: dateRange.startTime,
monthsBeforeCurrentMonth: monthDiff,
incomeAmount: item?.incomeAmount || 0, incomeAmount: item?.incomeAmount || 0,
expenseAmount: item?.expenseAmount || 0, expenseAmount: item?.expenseAmount || 0,
incompleteIncomeAmount: item ? item.incompleteIncomeAmount : true, incompleteIncomeAmount: item ? item.incompleteIncomeAmount : true,
@@ -145,7 +145,9 @@
<date-time-select <date-time-select
:disabled="loading || submitting" :disabled="loading || submitting"
:label="tt('Balance Time')" :label="tt('Balance Time')"
v-model="selectedAccount.balanceTime" :timezone-utc-offset="getDefaultTimezoneOffsetMinutes(selectedAccount)"
:model-value="selectedAccount.balanceTime"
@update:model-value="updateAccountBalanceTime(selectedAccount, $event)"
@error="onShowDateTimeError" /> @error="onShowDateTimeError" />
</v-col> </v-col>
<v-col cols="12" md="12"> <v-col cols="12" md="12">
@@ -210,7 +212,6 @@ import { ALL_ACCOUNT_COLORS } from '@/consts/color.ts';
import { Account } from '@/models/account.ts'; import { Account } from '@/models/account.ts';
import { isNumber } from '@/lib/common.ts'; import { isNumber } from '@/lib/common.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts';
import { generateRandomUUID } from '@/lib/misc.ts'; import { generateRandomUUID } from '@/lib/misc.ts';
import { import {
@@ -242,6 +243,9 @@ const {
allAccountTypes, allAccountTypes,
allAvailableMonthDays, allAvailableMonthDays,
isAccountSupportCreditCardStatementDate, isAccountSupportCreditCardStatementDate,
getCurrentUnixTimeForNewAccount,
getDefaultTimezoneOffsetMinutes,
updateAccountBalanceTime,
isNewAccount, isNewAccount,
addSubAccount, addSubAccount,
setAccount setAccount
@@ -275,7 +279,7 @@ const accountAmountTitle = computed<string>(() => {
const isAccountModified = computed<boolean>(() => { const isAccountModified = computed<boolean>(() => {
if (!editAccountId.value) { if (!editAccountId.value) {
return !account.value.equals(Account.createNewAccount(userStore.currentUserDefaultCurrency, account.value.balanceTime ?? getCurrentUnixTime())); return !account.value.equals(Account.createNewAccount(userStore.currentUserDefaultCurrency, account.value.balanceTime ?? getCurrentUnixTimeForNewAccount()));
} else { } else {
return true; return true;
} }
@@ -289,7 +293,7 @@ function open(options?: { id?: string, currentAccount?: Account, category?: numb
loading.value = true; loading.value = true;
submitting.value = false; submitting.value = false;
const newAccount = Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime()); const newAccount = Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
account.value.fillFrom(newAccount); account.value.fillFrom(newAccount);
subAccounts.value = []; subAccounts.value = [];
currentAccountIndex.value = -1; currentAccountIndex.value = -1;
@@ -149,7 +149,7 @@
<template #item.time="{ item }"> <template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span> <span>{{ getDisplayDateTime(item) }}</span>
<v-chip class="ms-1" variant="flat" color="secondary" size="x-small" <v-chip class="ms-1" variant="flat" color="secondary" size="x-small"
v-if="item.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimezone(item) }}</v-chip> v-if="!isSameAsDefaultTimezoneOffsetMinutes(item)">{{ getDisplayTimezone(item) }}</v-chip>
</template> </template>
<template #item.type="{ item }"> <template #item.type="{ item }">
<v-chip label variant="outlined" size="x-small" <v-chip label variant="outlined" size="x-small"
@@ -323,7 +323,6 @@ const {
startTime, startTime,
endTime, endTime,
reconciliationStatements, reconciliationStatements,
currentTimezoneOffsetMinutes,
fiscalYearStart, fiscalYearStart,
allChartTypes, allChartTypes,
allDateAggregationTypes, allDateAggregationTypes,
@@ -341,6 +340,7 @@ const {
setReconciliationStatements, setReconciliationStatements,
getDisplayTransactionType, getDisplayTransactionType,
getDisplayDateTime, getDisplayDateTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone, getDisplayTimezone,
getDisplaySourceAmount, getDisplaySourceAmount,
getDisplayDestinationAmount, getDisplayDestinationAmount,
+5 -4
View File
@@ -151,9 +151,10 @@ import {
} from '@/models/transaction.ts'; } from '@/models/transaction.ts';
import { import {
parseDateTimeFromUnixTime,
getShiftedDateRangeAndDateType, getShiftedDateRangeAndDateType,
getDateTypeByDateRange, getDateTypeByDateRange,
getDateRangeByDateType, getDateRangeByDateType
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
import { import {
@@ -188,7 +189,7 @@ const {
tt, tt,
getAllDateRanges, getAllDateRanges,
getCurrentNumeralSystemType, getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatDateRange formatDateRange
} = useI18n(); } = useI18n();
@@ -221,8 +222,8 @@ const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explor
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplore, true)); const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplore, true));
const canShiftDateRange = computed<boolean>(() => query.value.dateRangeType !== DateRange.All.type); const canShiftDateRange = computed<boolean>(() => query.value.dateRangeType !== DateRange.All.type);
const displayQueryDateRangeName = computed<string>(() => formatDateRange(query.value.dateRangeType, query.value.startTime, query.value.endTime)); const displayQueryDateRangeName = computed<string>(() => formatDateRange(query.value.dateRangeType, query.value.startTime, query.value.endTime));
const displayQueryStartTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.startTime)); const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.startTime)));
const displayQueryEndTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.endTime)); const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.endTime)));
const allTabs = computed<{ name: string, value: ExplorePageTabType }[]>(() => { const allTabs = computed<{ name: string, value: ExplorePageTabType }[]>(() => {
return [ return [
@@ -14,7 +14,7 @@
<template #item.time="{ item }"> <template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span> <span>{{ getDisplayDateTime(item) }}</span>
<v-chip class="ms-1" variant="flat" color="secondary" size="x-small" <v-chip class="ms-1" variant="flat" color="secondary" size="x-small"
v-if="item.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimezone(item) }}</v-chip> v-if="!isSameAsDefaultTimezoneOffsetMinutes(item)">{{ getDisplayTimezone(item) }}</v-chip>
</template> </template>
<template #item.type="{ item }"> <template #item.type="{ item }">
<v-chip label variant="outlined" size="x-small" <v-chip label variant="outlined" size="x-small"
@@ -76,7 +76,6 @@ import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { useUserStore } from '@/stores/user.ts'; import { useUserStore } from '@/stores/user.ts';
import { useExploresStore } from '@/stores/explore.ts'; import { useExploresStore } from '@/stores/explore.ts';
@@ -89,7 +88,7 @@ import {
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
import { import {
@@ -109,19 +108,17 @@ const emit = defineEmits<{
const { const {
tt, tt,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatUnixTimeToGregorianDefaultDateTime, formatDateTimeToGregorianDefaultDateTime,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping, formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
const settingsStore = useSettingsStore();
const userStore = useUserStore(); const userStore = useUserStore();
const exploresStore = useExploresStore(); const exploresStore = useExploresStore();
const currentPage = ref<number>(1); const currentPage = ref<number>(1);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency); const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => exploresStore.filteredTransactions); const filteredTransactions = computed<TransactionInsightDataItem[]>(() => exploresStore.filteredTransactions);
@@ -163,7 +160,12 @@ const dataTableHeaders = computed<object[]>(() => {
}); });
function getDisplayDateTime(transaction: TransactionInsightDataItem): string { function getDisplayDateTime(transaction: TransactionInsightDataItem): string {
return formatUnixTimeToLongDateTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDateTime(dateTime);
}
function isSameAsDefaultTimezoneOffsetMinutes(transaction: TransactionInsightDataItem): boolean {
return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time);
} }
function getDisplayTimezone(transaction: TransactionInsightDataItem): string { function getDisplayTimezone(transaction: TransactionInsightDataItem): string {
@@ -234,7 +236,7 @@ function buildExportResults(): { headers: string[], data: string[][] } | undefin
], ],
data: filteredTransactions.value data: filteredTransactions.value
.map(transaction => { .map(transaction => {
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value).getUnixTime(); const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
const type = getDisplayTransactionType(transaction); const type = getDisplayTransactionType(transaction);
let categoryName = transaction.secondaryCategoryName; let categoryName = transaction.secondaryCategoryName;
@@ -254,7 +256,7 @@ function buildExportResults(): { headers: string[], data: string[][] } | undefin
const description = transaction.comment || ''; const description = transaction.comment || '';
return [ return [
formatUnixTimeToGregorianDefaultDateTime(transactionTime), formatDateTimeToGregorianDefaultDateTime(transactionTime),
type, type,
categoryName, categoryName,
displayAmount, displayAmount,
@@ -41,7 +41,7 @@ import { DISPLAY_HIDDEN_AMOUNT, INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numera
import { type TransactionMonthlyIncomeAndExpenseData } from '@/models/transaction.ts'; import { type TransactionMonthlyIncomeAndExpenseData } from '@/models/transaction.ts';
import { getUnixTimeBeforeUnixTime, getThisMonthFirstUnixTime } from '@/lib/datetime.ts'; import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { getExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts'; import { getExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
export interface MonthlyIncomeAndExpenseCardClickEvent { export interface MonthlyIncomeAndExpenseCardClickEvent {
@@ -64,7 +64,7 @@ const emit = defineEmits<{
const { const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
formatUnixTimeToGregorianLikeShortMonth, formatDateTimeToGregorianLikeShortMonth,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -98,11 +98,9 @@ const chartOptions = computed<object>(() => {
const expenseIncomeAmountColor = getExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor, props.isDarkMode); const expenseIncomeAmountColor = getExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor, props.isDarkMode);
if (props.data) { if (props.data) {
const currentMonthFirstUnixTime = getThisMonthFirstUnixTime();
for (const item of props.data) { for (const item of props.data) {
const monthFirstUnixTime = getUnixTimeBeforeUnixTime(currentMonthFirstUnixTime, item.monthsBeforeCurrentMonth, 'months'); const monthStartDateTime = parseDateTimeFromUnixTime(item.monthStartTime);
const monthShortName = formatUnixTimeToGregorianLikeShortMonth(monthFirstUnixTime); const monthShortName = formatDateTimeToGregorianLikeShortMonth(monthStartDateTime);
monthNames.push(monthShortName); monthNames.push(monthShortName);
incomeAmounts.push(item.incomeAmount); incomeAmounts.push(item.incomeAmount);
+4 -6
View File
@@ -534,8 +534,8 @@
<td class="transaction-table-column-time"> <td class="transaction-table-column-time">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<span>{{ getDisplayTime(transaction) }}</span> <span>{{ getDisplayTime(transaction) }}</span>
<span class="text-caption" v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimezone(transaction) }}</span> <span class="text-caption" v-if="!isSameAsDefaultTimezoneOffsetMinutes(transaction)">{{ getDisplayTimezone(transaction) }}</span>
<v-tooltip activator="parent" v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimeInDefaultTimezone(transaction) }}</v-tooltip> <v-tooltip activator="parent" v-if="!isSameAsDefaultTimezoneOffsetMinutes(transaction)">{{ getDisplayTimeInDefaultTimezone(transaction) }}</v-tooltip>
</div> </div>
</td> </td>
<td class="transaction-table-column-category"> <td class="transaction-table-column-category">
@@ -691,8 +691,6 @@ import {
import { import {
getCurrentUnixTime, getCurrentUnixTime,
parseDateTimeFromUnixTime, parseDateTimeFromUnixTime,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
getDayFirstUnixTimeBySpecifiedUnixTime, getDayFirstUnixTimeBySpecifiedUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime, getYearMonthLastUnixTime,
@@ -775,7 +773,6 @@ const {
customMinDatetime, customMinDatetime,
customMaxDatetime, customMaxDatetime,
currentCalendarDate, currentCalendarDate,
currentTimezoneOffsetMinutes,
firstDayOfWeek, firstDayOfWeek,
fiscalYearStart, fiscalYearStart,
defaultCurrency, defaultCurrency,
@@ -809,6 +806,7 @@ const {
transactionCalendarMaxDate, transactionCalendarMaxDate,
currentMonthTransactionData, currentMonthTransactionData,
hasSubCategoryInQuery, hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
canAddTransaction, canAddTransaction,
getDisplayTime, getDisplayTime,
getDisplayLongDate, getDisplayLongDate,
@@ -1234,7 +1232,7 @@ function changePageType(type: number): void {
function changeDateFilter(dateRange: TimeRangeAndDateType | number | null): void { function changeDateFilter(dateRange: TimeRangeAndDateType | number | null): void {
if (dateRange === DateRange.Custom.type || (isObject(dateRange) && dateRange.dateType === DateRange.Custom.type && !dateRange.minTime && !dateRange.maxTime)) { // Custom if (dateRange === DateRange.Custom.type || (isObject(dateRange) && dateRange.dateType === DateRange.Custom.type && !dateRange.minTime && !dateRange.maxTime)) { // Custom
if (!query.value.minTime || !query.value.maxTime) { if (!query.value.minTime || !query.value.maxTime) {
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes()); customMaxDatetime.value = getCurrentUnixTime();
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value); customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
} else { } else {
customMaxDatetime.value = query.value.maxTime; customMaxDatetime.value = query.value.maxTime;
@@ -81,7 +81,7 @@
<template #item.time="{ item }"> <template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span> <span>{{ getDisplayDateTime(item) }}</span>
<v-chip class="ms-1" variant="flat" color="grey" size="x-small" <v-chip class="ms-1" variant="flat" color="grey" size="x-small"
v-if="item.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimezone(item) }}</v-chip> v-if="!isSameAsDefaultTimezoneOffsetMinutes(item)">{{ getDisplayTimezone(item) }}</v-chip>
</template> </template>
<template #item.type="{ value }"> <template #item.type="{ value }">
<v-chip label color="secondary" variant="outlined" size="x-small" v-if="value === TransactionType.ModifyBalance">{{ tt('Modify Balance') }}</v-chip> <v-chip label color="secondary" variant="outlined" size="x-small" v-if="value === TransactionType.ModifyBalance">{{ tt('Modify Balance') }}</v-chip>
@@ -427,7 +427,9 @@ import {
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import { import {
getUtcOffsetByUtcOffsetMinutes, getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
import { formatCoordinate } from '@/lib/coordinate.ts'; import { formatCoordinate } from '@/lib/coordinate.ts';
import { import {
@@ -495,7 +497,7 @@ const props = defineProps<{
const { const {
tt, tt,
getCurrentNumeralSystemType, getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
formatAmountToLocalizedNumeralsWithCurrency, formatAmountToLocalizedNumeralsWithCurrency,
getCategorizedAccountsWithDisplayBalance getCategorizedAccountsWithDisplayBalance
} = useI18n(); } = useI18n();
@@ -536,7 +538,6 @@ const currentDescriptionFilterValue = ref<string | null>(null);
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType()); const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance); const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency); const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType); const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType);
@@ -1129,8 +1130,8 @@ const displayFilterCustomDateRange = computed<string>(() => {
return ''; return '';
} }
const minDisplayTime = formatUnixTimeToLongDateTime(filters.value.minDatetime); const minDisplayTime = formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(filters.value.minDatetime));
const maxDisplayTime = formatUnixTimeToLongDateTime(filters.value.maxDatetime); const maxDisplayTime = formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(filters.value.maxDatetime));
return `${minDisplayTime} - ${maxDisplayTime}` return `${minDisplayTime} - ${maxDisplayTime}`
}); });
@@ -1272,7 +1273,12 @@ function isTagValid(tagIds: string[], tagIndex: number): boolean {
} }
function getDisplayDateTime(transaction: ImportTransaction): string { function getDisplayDateTime(transaction: ImportTransaction): string {
return formatUnixTimeToLongDateTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset)
return formatDateTimeToLongDateTime(dateTime);
}
function isSameAsDefaultTimezoneOffsetMinutes(transaction: ImportTransaction): boolean {
return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time);
} }
function getDisplayTimezone(transaction: ImportTransaction): string { function getDisplayTimezone(transaction: ImportTransaction): string {
@@ -54,7 +54,10 @@ import { KnownFileType } from '@/core/file.ts';
import type { ImportTransactionRequest, ImportTransactionRequestItem } from '@/models/imported_transaction.ts'; import type { ImportTransactionRequest, ImportTransactionRequestItem } from '@/models/imported_transaction.ts';
import { isDefined } from '@/lib/common.ts'; import { isDefined } from '@/lib/common.ts';
import { getBrowserTimezoneOffsetMinutes } from '@/lib/datetime.ts'; import {
getTimezoneOffsetMinutes,
getCurrentUnixTime
} from '@/lib/datetime.ts';
import { import {
openTextFileContent, openTextFileContent,
startDownloadFile startDownloadFile
@@ -121,7 +124,7 @@ function parse(row, index) {
return { return {
time: row[0], // ${tt('sample.importTransactionCustomScript.fieldTimeDescription')} time: row[0], // ${tt('sample.importTransactionCustomScript.fieldTimeDescription')}
utcOffset: '${getBrowserTimezoneOffsetMinutes()}', // ${tt('sample.importTransactionCustomScript.fieldUtcOffsetDescription')} utcOffset: '${getTimezoneOffsetMinutes(getCurrentUnixTime())}', // ${tt('sample.importTransactionCustomScript.fieldUtcOffsetDescription')}
type: TransactionType.Expense, // ${tt('sample.importTransactionCustomScript.fieldTypeDescription')} type: TransactionType.Expense, // ${tt('sample.importTransactionCustomScript.fieldTypeDescription')}
categoryName: row[4], // ${tt('sample.importTransactionCustomScript.fieldCategoryNameDescription')} categoryName: row[4], // ${tt('sample.importTransactionCustomScript.fieldCategoryNameDescription')}
sourceAccountName: row[5], // ${tt('sample.importTransactionCustomScript.fieldSourceAccountNameDescription')} sourceAccountName: row[5], // ${tt('sample.importTransactionCustomScript.fieldSourceAccountNameDescription')}
@@ -250,7 +250,9 @@
:readonly="mode === TransactionEditPageMode.View" :readonly="mode === TransactionEditPageMode.View"
:disabled="loading || submitting || (mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance)" :disabled="loading || submitting || (mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance)"
:label="tt('Transaction Time')" :label="tt('Transaction Time')"
v-model="transaction.time" :timezone-utc-offset="transaction.utcOffset"
:model-value="transaction.time"
@update:model-value="updateTransactionTime"
@error="onShowDateTimeError" /> @error="onShowDateTimeError" />
</v-col> </v-col>
<v-col cols="12" md="6" v-if="type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"> <v-col cols="12" md="6" v-if="type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type">
@@ -274,7 +276,8 @@
:placeholder="!transaction.timeZone && transaction.timeZone !== '' ? `(${transactionDisplayTimezone}) ${transactionTimezoneTimeDifference}` : tt('Timezone')" :placeholder="!transaction.timeZone && transaction.timeZone !== '' ? `(${transactionDisplayTimezone}) ${transactionTimezoneTimeDifference}` : tt('Timezone')"
:items="allTimezones" :items="allTimezones"
:no-data-text="tt('No results')" :no-data-text="tt('No results')"
v-model="transaction.timeZone" :model-value="transaction.timeZone"
@update:model-value="updateTransactionTimezone"
> >
<template #selection="{ item }"> <template #selection="{ item }">
<span class="text-truncate" v-if="transaction.timeZone || transaction.timeZone === ''"> <span class="text-truncate" v-if="transaction.timeZone || transaction.timeZone === ''">
@@ -642,6 +645,8 @@ const {
inputEmptyProblemMessage, inputEmptyProblemMessage,
inputIsEmpty, inputIsEmpty,
createNewTransactionModel, createNewTransactionModel,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData, swapTransactionData,
getTransactionPictureUrl getTransactionPictureUrl
} = useTransactionEditPageBase(props.type); } = useTransactionEditPageBase(props.type);
@@ -714,7 +719,7 @@ const isTransactionModified = computed<boolean>(() => {
} }
}); });
function setTransaction(newTransaction: Transaction | null, options: SetTransactionOptions, setContextData: boolean, convertContextTime: boolean): void { function setTransaction(newTransaction: Transaction | null, options: SetTransactionOptions, setContextData: boolean): void {
setTransactionModelByTransaction( setTransactionModelByTransaction(
transaction.value, transaction.value,
newTransaction, newTransaction,
@@ -735,8 +740,7 @@ function setTransaction(newTransaction: Transaction | null, options: SetTransact
tagIds: options.tagIds, tagIds: options.tagIds,
comment: options.comment comment: options.comment
}, },
setContextData, setContextData
convertContextTime
); );
} }
@@ -758,7 +762,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
initTagIds.value = options.tagIds; initTagIds.value = options.tagIds;
const newTransaction = createNewTransactionModel(options.type); const newTransaction = createNewTransactionModel(options.type);
setTransaction(newTransaction, options, true, false); setTransaction(newTransaction, options, true);
const promises: Promise<unknown>[] = [ const promises: Promise<unknown>[] = [
accountsStore.loadAllAccounts({ force: false }), accountsStore.loadAllAccounts({ force: false }),
@@ -769,7 +773,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
if (props.type === TransactionEditPageType.Transaction) { if (props.type === TransactionEditPageType.Transaction) {
if (options && options.id) { if (options && options.id) {
if (options.currentTransaction) { if (options.currentTransaction) {
setTransaction(options.currentTransaction, options, true, true); setTransaction(options.currentTransaction, options, true);
} }
mode.value = TransactionEditPageMode.View; mode.value = TransactionEditPageMode.View;
@@ -781,10 +785,10 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
editId.value = null; editId.value = null;
if (options.template) { if (options.template) {
setTransaction(options.template, options, false, false); setTransaction(options.template, options, false);
addByTemplateId.value = options.template.id; addByTemplateId.value = options.template.id;
} else if (!options.noTransactionDraft && (settingsStore.appSettings.autoSaveTransactionDraft === 'enabled' || settingsStore.appSettings.autoSaveTransactionDraft === 'confirmation') && transactionsStore.transactionDraft) { } else if (!options.noTransactionDraft && (settingsStore.appSettings.autoSaveTransactionDraft === 'enabled' || settingsStore.appSettings.autoSaveTransactionDraft === 'confirmation') && transactionsStore.transactionDraft) {
setTransaction(Transaction.ofDraft(transactionsStore.transactionDraft), options, false, false); setTransaction(Transaction.ofDraft(transactionsStore.transactionDraft), options, false);
} }
if (settingsStore.appSettings.autoGetCurrentGeoLocation if (settingsStore.appSettings.autoGetCurrentGeoLocation
@@ -809,7 +813,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
if (options && options.id) { if (options && options.id) {
if (options.currentTemplate) { if (options.currentTemplate) {
setTransaction(options.currentTemplate, options, false, false); setTransaction(options.currentTemplate, options, false);
(transaction.value as TransactionTemplate).fillFrom(options.currentTemplate); (transaction.value as TransactionTemplate).fillFrom(options.currentTemplate);
} }
@@ -850,11 +854,11 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
if (props.type === TransactionEditPageType.Transaction && options && options.id && responses[3] && responses[3] instanceof Transaction) { if (props.type === TransactionEditPageType.Transaction && options && options.id && responses[3] && responses[3] instanceof Transaction) {
const transaction: Transaction = responses[3]; const transaction: Transaction = responses[3];
setTransaction(transaction, options, true, true); setTransaction(transaction, options, true);
originalTransactionEditable.value = transaction.editable; originalTransactionEditable.value = transaction.editable;
} else if (props.type === TransactionEditPageType.Template && options && options.id && responses[3] && responses[3] instanceof TransactionTemplate) { } else if (props.type === TransactionEditPageType.Template && options && options.id && responses[3] && responses[3] instanceof TransactionTemplate) {
const template: TransactionTemplate = responses[3]; const template: TransactionTemplate = responses[3];
setTransaction(template, options, false, false); setTransaction(template, options, false);
if (!(transaction.value instanceof TransactionTemplate)) { if (!(transaction.value instanceof TransactionTemplate)) {
transaction.value = TransactionTemplate.createNewTransactionTemplate(transaction.value); transaction.value = TransactionTemplate.createNewTransactionTemplate(transaction.value);
@@ -862,7 +866,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
(transaction.value as TransactionTemplate).fillFrom(template); (transaction.value as TransactionTemplate).fillFrom(template);
} else { } else {
setTransaction(null, options, true, true); setTransaction(null, options, true);
} }
loading.value = false; loading.value = false;
@@ -1003,7 +1007,7 @@ function duplicate(withTime?: boolean, withGeoLocation?: boolean): void {
if (!withTime) { if (!withTime) {
transaction.value.time = getCurrentUnixTime(); transaction.value.time = getCurrentUnixTime();
transaction.value.timeZone = settingsStore.appSettings.timeZone; transaction.value.timeZone = settingsStore.appSettings.timeZone;
transaction.value.utcOffset = getTimezoneOffsetMinutes(transaction.value.timeZone); transaction.value.utcOffset = getTimezoneOffsetMinutes(transaction.value.time, transaction.value.timeZone);
} }
if (!withGeoLocation) { if (!withGeoLocation) {
@@ -218,6 +218,7 @@ import { type UserExternalAuthInfoResponse } from '@/models/user_external_auth.t
import { type TokenInfoResponse, SessionDeviceType, SessionInfo } from '@/models/token.ts'; import { type TokenInfoResponse, SessionDeviceType, SessionInfo } from '@/models/token.ts';
import { isEquals } from '@/lib/common.ts'; import { isEquals } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { parseSessionInfo } from '@/lib/session.ts'; import { parseSessionInfo } from '@/lib/session.ts';
import { import {
isAPITokenEnabled, isAPITokenEnabled,
@@ -253,7 +254,7 @@ class DesktopPageLinkedThirdPartyLogin {
this.externalAuthType = externalAuthInfoResponse.externalAuthType; this.externalAuthType = externalAuthInfoResponse.externalAuthType;
this.linked = externalAuthInfoResponse.linked; this.linked = externalAuthInfoResponse.linked;
this.externalUsername = externalAuthInfoResponse.externalUsername ? externalAuthInfoResponse.externalUsername : '-'; this.externalUsername = externalAuthInfoResponse.externalUsername ? externalAuthInfoResponse.externalUsername : '-';
this.createdAt = externalAuthInfoResponse.createdAt ? formatUnixTimeToLongDateTime(externalAuthInfoResponse.createdAt) : '-'; this.createdAt = externalAuthInfoResponse.createdAt ? formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(externalAuthInfoResponse.createdAt)) : '-';
if (externalAuthInfoResponse.externalAuthCategory === 'oauth2') { if (externalAuthInfoResponse.externalAuthCategory === 'oauth2') {
this.displayName = getLocalizedOAuth2ProviderName(externalAuthInfoResponse.externalAuthType, getOIDCCustomDisplayNames()); this.displayName = getLocalizedOAuth2ProviderName(externalAuthInfoResponse.externalAuthType, getOIDCCustomDisplayNames());
@@ -277,7 +278,7 @@ class DesktopPageSessionInfo extends SessionInfo {
public constructor(sessionInfo: SessionInfo) { public constructor(sessionInfo: SessionInfo) {
super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.deviceName, sessionInfo.lastSeen); super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.deviceName, sessionInfo.lastSeen);
this.icon = this.getTokenIcon(sessionInfo.deviceType); this.icon = this.getTokenIcon(sessionInfo.deviceType);
this.lastSeenDateTime = sessionInfo.lastSeen ? formatUnixTimeToLongDateTime(sessionInfo.lastSeen) : '-'; this.lastSeenDateTime = sessionInfo.lastSeen ? formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(sessionInfo.lastSeen)) : '-';
} }
private getTokenIcon(deviceType: SessionDeviceType): string { private getTokenIcon(deviceType: SessionDeviceType): string {
@@ -306,7 +307,7 @@ type SnackBarType = InstanceType<typeof SnackBar>;
const { const {
tt, tt,
formatUnixTimeToLongDateTime, formatDateTimeToLongDateTime,
getLocalizedOAuth2ProviderName, getLocalizedOAuth2ProviderName,
setLanguage setLanguage
} = useI18n(); } = useI18n();
+8 -3
View File
@@ -129,6 +129,7 @@ import { useUserStore } from '@/stores/user.ts';
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
import { findNameByValue } from '@/lib/common.ts'; import { findNameByValue } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { getClientDisplayVersion, getDesktopVersionPath } from '@/lib/version.ts'; import { getClientDisplayVersion, getDesktopVersionPath } from '@/lib/version.ts';
import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.ts'; import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.ts';
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts'; import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
@@ -137,7 +138,7 @@ const props = defineProps<{
f7router: Router.Router; f7router: Router.Router;
}>(); }>();
const { tt, formatUnixTimeToLongDate, initLocale } = useI18n(); const { tt, formatDateTimeToLongDate, initLocale } = useI18n();
const { showToast, showConfirm } = useI18nUIComponents(); const { showToast, showConfirm } = useI18nUIComponents();
const { allThemes, allTimezones, timeZone, isAutoUpdateExchangeRatesData, showAccountBalance } = useAppSettingPageBase(); const { allThemes, allTimezones, timeZone, isAutoUpdateExchangeRatesData, showAccountBalance } = useAppSettingPageBase();
@@ -197,8 +198,12 @@ const isEnableAnimate = computed<boolean>({
const isEnableApplicationLock = computed<boolean>(() => settingsStore.appSettings.applicationLock); const isEnableApplicationLock = computed<boolean>(() => settingsStore.appSettings.applicationLock);
const exchangeRatesLastUpdateDate = computed<string>(() => { const exchangeRatesLastUpdateDate = computed<string>(() => {
const exchangeRatesLastUpdateTime = exchangeRatesStore.exchangeRatesLastUpdateTime; if (!exchangeRatesStore.exchangeRatesLastUpdateTime) {
return exchangeRatesLastUpdateTime ? formatUnixTimeToLongDate(exchangeRatesLastUpdateTime) : ''; return '';
}
const exchangeRatesLastUpdateTime = parseDateTimeFromUnixTime(exchangeRatesStore.exchangeRatesLastUpdateTime);
return formatDateTimeToLongDate(exchangeRatesLastUpdateTime);
}); });
function switchToDesktopVersion(): void { function switchToDesktopVersion(): void {
+15 -8
View File
@@ -233,8 +233,10 @@
</div> </div>
</template> </template>
<date-time-selection-sheet :init-mode="accountContext.balanceDateTimeSheetMode" <date-time-selection-sheet :init-mode="accountContext.balanceDateTimeSheetMode"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(account)"
:model-value="account.balanceTime"
v-model:show="accountContext.showBalanceDateTimeSheet" v-model:show="accountContext.showBalanceDateTimeSheet"
v-model="account.balanceTime"> @update:model-value="updateAccountBalanceTime(account, $event)">
</date-time-selection-sheet> </date-time-selection-sheet>
</f7-list-item> </f7-list-item>
@@ -475,8 +477,10 @@
</div> </div>
</template> </template>
<date-time-selection-sheet :init-mode="subAccountContexts[idx]!.balanceDateTimeSheetMode" <date-time-selection-sheet :init-mode="subAccountContexts[idx]!.balanceDateTimeSheetMode"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(subAccount)"
:model-value="subAccount.balanceTime"
v-model:show="subAccountContexts[idx]!.showBalanceDateTimeSheet" v-model:show="subAccountContexts[idx]!.showBalanceDateTimeSheet"
v-model="subAccount.balanceTime"> @update:model-value="updateAccountBalanceTime(subAccount, $event)">
</date-time-selection-sheet> </date-time-selection-sheet>
</f7-list-item> </f7-list-item>
@@ -538,8 +542,7 @@ import { isDefined, findDisplayNameByType } from '@/lib/common.ts';
import { generateRandomUUID } from '@/lib/misc.ts'; import { generateRandomUUID } from '@/lib/misc.ts';
import { import {
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes, parseDateTimeFromUnixTimeWithTimezoneOffset
getActualUnixTimeForStore
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
interface AccountContext { interface AccountContext {
@@ -561,8 +564,8 @@ const {
tt, tt,
getAllCurrencies, getAllCurrencies,
getCurrencyName, getCurrencyName,
formatUnixTimeToLongDate, formatDateTimeToLongDate,
formatUnixTimeToLongTime, formatDateTimeToLongTime,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
@@ -582,7 +585,9 @@ const {
allAccountTypes, allAccountTypes,
allAvailableMonthDays, allAvailableMonthDays,
isAccountSupportCreditCardStatementDate, isAccountSupportCreditCardStatementDate,
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate, getAccountCreditCardStatementDate,
updateAccountBalanceTime,
isNewAccount, isNewAccount,
addSubAccount, addSubAccount,
setAccount setAccount
@@ -621,7 +626,8 @@ function formatAccountBalanceDate(account: Account): string {
return ''; return '';
} }
return formatUnixTimeToLongDate(getActualUnixTimeForStore(account.balanceTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
return formatDateTimeToLongDate(dateTime);
} }
function formatAccountBalanceTime(account: Account): string { function formatAccountBalanceTime(account: Account): string {
@@ -629,7 +635,8 @@ function formatAccountBalanceTime(account: Account): string {
return ''; return '';
} }
return formatUnixTimeToLongTime(getActualUnixTimeForStore(account.balanceTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
return formatDateTimeToLongTime(dateTime);
} }
function init(): void { function init(): void {
@@ -51,10 +51,10 @@
</template> </template>
<template #footer> <template #footer>
<div v-if="dateRange.isUserCustomRange && queryDateRangeType === dateRange.type && startTime && endTime"> <div v-if="dateRange.isUserCustomRange && queryDateRangeType === dateRange.type && startTime && endTime">
<span>{{ displayStartTime }}</span> <span>{{ displayStartDateTime }}</span>
<span>&nbsp;-&nbsp;</span> <span>&nbsp;-&nbsp;</span>
<br/> <br/>
<span>{{ displayEndTime }}</span> <span>{{ displayEndDateTime }}</span>
</div> </div>
</template> </template>
</f7-list-item> </f7-list-item>
@@ -227,7 +227,7 @@
<div class="transaction-footer display-flex justify-content-space-between"> <div class="transaction-footer display-flex justify-content-space-between">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<span>{{ getDisplayTime(item.transaction) }}</span> <span>{{ getDisplayTime(item.transaction) }}</span>
<span v-if="item.transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ `(${getDisplayTimezone(item.transaction)})` }}</span> <span style="margin-inline-start: 4px" v-if="!isSameAsDefaultTimezoneOffsetMinutes(item.transaction)">{{ `(${getDisplayTimezone(item.transaction)})` }}</span>
</div> </div>
<div class="account-balance flex-shrink-1"> <div class="account-balance flex-shrink-1">
<span>{{ isCurrentLiabilityAccount ? tt('Outstanding Balance') : tt('Balance') }}</span> <span>{{ isCurrentLiabilityAccount ? tt('Outstanding Balance') : tt('Balance') }}</span>
@@ -388,7 +388,6 @@ const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
getAllDateRanges, getAllDateRanges,
formatUnixTimeToLongDateTime,
formatNumberToLocalizedNumerals formatNumberToLocalizedNumerals
} = useI18n(); } = useI18n();
@@ -402,7 +401,6 @@ const {
firstDayOfWeek, firstDayOfWeek,
fiscalYearStart, fiscalYearStart,
allDateAggregationTypes, allDateAggregationTypes,
currentTimezoneOffsetMinutes,
isCurrentLiabilityAccount, isCurrentLiabilityAccount,
currentAccount, currentAccount,
currentAccountCurrency, currentAccountCurrency,
@@ -416,6 +414,7 @@ const {
setReconciliationStatements, setReconciliationStatements,
getDisplayDate, getDisplayDate,
getDisplayTime, getDisplayTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone, getDisplayTimezone,
getDisplaySourceAmount, getDisplaySourceAmount,
getDisplayDestinationAmount, getDisplayDestinationAmount,
@@ -446,8 +445,6 @@ const virtualDataItems = ref<ReconciliationStatementVirtualListData>({
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection()); const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
const validQuery = computed(() => currentAccount.value && currentAccount.value.type === AccountType.SingleAccount.type); const validQuery = computed(() => currentAccount.value && currentAccount.value.type === AccountType.SingleAccount.type);
const allAvailableDateRanges = computed(() => getAllDateRanges(DateRangeScene.Normal, true, !!accountsStore.getAccountStatementDate(accountId.value))); const allAvailableDateRanges = computed(() => getAllDateRanges(DateRangeScene.Normal, true, !!accountsStore.getAccountStatementDate(accountId.value)));
const displayStartTime = computed<string>(() => formatUnixTimeToLongDateTime(startTime.value));
const displayEndTime = computed<string>(() => formatUnixTimeToLongDateTime(endTime.value));
const allReconciliationStatementVirtualListItems = computed<ReconciliationStatementVirtualListItem[]>(() => { const allReconciliationStatementVirtualListItems = computed<ReconciliationStatementVirtualListItem[]>(() => {
const ret: ReconciliationStatementVirtualListItem[] = []; const ret: ReconciliationStatementVirtualListItem[] = [];
@@ -124,8 +124,9 @@ import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts'; import { useSettingsStore } from '@/stores/setting.ts';
import { TextDirection } from '@/core/text.ts'; import { TextDirection } from '@/core/text.ts';
import { type DateTime } from '@/core/datetime.ts';
import { FontSize } from '@/core/font.ts'; import { FontSize } from '@/core/font.ts';
import { parseDateTimeFromUnixTime, getCurrentUnixTime } from '@/lib/datetime.ts'; import { getCurrentDateTime } from '@/lib/datetime.ts';
import { setAppFontSize, getFontSizePreviewClassName } from '@/lib/ui/mobile.ts'; import { setAppFontSize, getFontSizePreviewClassName } from '@/lib/ui/mobile.ts';
const props = defineProps<{ const props = defineProps<{
@@ -136,23 +137,23 @@ const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
getWeekdayShortName, getWeekdayShortName,
getCalendarDisplayDayOfMonthFromUnixTime, getCalendarDisplayDayOfMonthFromDateTime,
formatUnixTimeToShortTime, formatDateTimeToShortTime,
formatUnixTimeToGregorianLikeLongYearMonth, formatDateTimeToGregorianLikeLongYearMonth,
formatAmountToLocalizedNumeralsWithCurrency formatAmountToLocalizedNumeralsWithCurrency
} = useI18n(); } = useI18n();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const currentUnixTime = ref<number>(getCurrentUnixTime()); const currentDateTime = ref<DateTime>(getCurrentDateTime());
const fontSize = ref<number>(settingsStore.appSettings.fontSize); const fontSize = ref<number>(settingsStore.appSettings.fontSize);
const textDirection = computed<string>(() => getCurrentLanguageTextDirection()); const textDirection = computed<string>(() => getCurrentLanguageTextDirection());
const fontSizePreviewClassName = computed<string>(() => getFontSizePreviewClassName(fontSize.value)); const fontSizePreviewClassName = computed<string>(() => getFontSizePreviewClassName(fontSize.value));
const currentLongYearMonth = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(currentUnixTime.value)); const currentLongYearMonth = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(currentDateTime.value));
const currentDayOfMonth = computed<string>(() => getCalendarDisplayDayOfMonthFromUnixTime(currentUnixTime.value)); const currentDayOfMonth = computed<string>(() => getCalendarDisplayDayOfMonthFromDateTime(currentDateTime.value));
const currentDayOfWeek = computed<string>(() => getWeekdayShortName(parseDateTimeFromUnixTime(currentUnixTime.value).getWeekDay())); const currentDayOfWeek = computed<string>(() => getWeekdayShortName(currentDateTime.value.getWeekDay()));
const currentShortTime = computed<string>(() => formatUnixTimeToShortTime(currentUnixTime.value)); const currentShortTime = computed<string>(() => formatDateTimeToShortTime(currentDateTime.value));
function getFontSizeName(): string { function getFontSizeName(): string {
return ''; return '';
+20 -13
View File
@@ -251,8 +251,10 @@
</div> </div>
</template> </template>
<date-time-selection-sheet :init-mode="transactionDateTimeSheetMode" <date-time-selection-sheet :init-mode="transactionDateTimeSheetMode"
:timezone-utc-offset="transaction.utcOffset"
:model-value="transaction.time"
v-model:show="showTransactionDateTimeSheet" v-model:show="showTransactionDateTimeSheet"
v-model="transaction.time"> @update:model-value="updateTransactionTime">
</date-time-selection-sheet> </date-time-selection-sheet>
</f7-list-item> </f7-list-item>
@@ -323,8 +325,9 @@
:filter-placeholder="tt('Timezone')" :filter-placeholder="tt('Timezone')"
:filter-no-items-text="tt('No results')" :filter-no-items-text="tt('No results')"
:items="allTimezones" :items="allTimezones"
:model-value="transaction.timeZone"
v-model:show="showTimezonePopup" v-model:show="showTimezonePopup"
v-model="transaction.timeZone"> @update:model-value="updateTransactionTimezone">
</list-item-selection-popup> </list-item-selection-popup>
</f7-list-item> </f7-list-item>
@@ -512,10 +515,9 @@ import type { TransactionPictureInfoBasicResponse } from '@/models/transaction_p
import { Transaction } from '@/models/transaction.ts'; import { Transaction } from '@/models/transaction.ts';
import { import {
getActualUnixTimeForStore,
getBrowserTimezoneOffsetMinutes,
getTimezoneOffset, getTimezoneOffset,
getTimezoneOffsetMinutes getTimezoneOffsetMinutes,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts'; } from '@/lib/datetime.ts';
import { formatCoordinate } from '@/lib/coordinate.ts'; import { formatCoordinate } from '@/lib/coordinate.ts';
import { generateRandomUUID } from '@/lib/misc.ts'; import { generateRandomUUID } from '@/lib/misc.ts';
@@ -536,8 +538,8 @@ const {
tt, tt,
getMultiMonthdayShortNames, getMultiMonthdayShortNames,
getMultiWeekdayLongNames, getMultiWeekdayLongNames,
formatUnixTimeToLongDate, formatDateTimeToLongDate,
formatUnixTimeToLongTime, formatDateTimeToLongTime,
formatGregorianTextualYearMonthDayToLongDate, formatGregorianTextualYearMonthDayToLongDate,
parseAmountFromLocalizedNumerals parseAmountFromLocalizedNumerals
} = useI18n(); } = useI18n();
@@ -589,6 +591,8 @@ const {
geoLocationStatusInfo, geoLocationStatusInfo,
inputEmptyProblemMessage, inputEmptyProblemMessage,
inputIsEmpty, inputIsEmpty,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData, swapTransactionData,
getDisplayAmount, getDisplayAmount,
getTransactionPictureUrl getTransactionPictureUrl
@@ -655,19 +659,23 @@ const destinationAmountClass = computed<Record<string, boolean>>(() => {
const transactionDisplayDate = computed<string>(() => { const transactionDisplayDate = computed<string>(() => {
if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) { if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) {
return formatUnixTimeToLongDate(getActualUnixTimeForStore(transaction.value.time, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, transaction.value.utcOffset);
return formatDateTimeToLongDate(dateTime);
} }
return formatUnixTimeToLongDate(getActualUnixTimeForStore(transaction.value.time, transaction.value.utcOffset, getBrowserTimezoneOffsetMinutes())); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, getTimezoneOffsetMinutes(transaction.value.time));
return formatDateTimeToLongDate(dateTime);
}); });
const transactionDisplayTime = computed<string>(() => { const transactionDisplayTime = computed<string>(() => {
if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) { if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) {
return formatUnixTimeToLongTime(getActualUnixTimeForStore(transaction.value.time, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, transaction.value.utcOffset);
return formatDateTimeToLongTime(dateTime);
} }
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(settingsStore.appSettings.timeZone)); const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, getTimezoneOffsetMinutes(transaction.value.time));
return `${formatUnixTimeToLongTime(getActualUnixTimeForStore(transaction.value.time, transaction.value.utcOffset, getBrowserTimezoneOffsetMinutes()))} (UTC${utcOffset})`; const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(transaction.value.time));
return `${formatDateTimeToLongTime(dateTime)} (UTC${utcOffset})`;
}); });
const transactionDisplayTimezoneName = computed<string>(() => { const transactionDisplayTimezoneName = computed<string>(() => {
@@ -949,7 +957,6 @@ function init(): void {
tagIds: query['tagIds'], tagIds: query['tagIds'],
comment: query['comment'] comment: query['comment']
}, },
pageTypeAndMode.type === TransactionEditPageType.Transaction && (mode.value === TransactionEditPageMode.Edit || mode.value === TransactionEditPageMode.View),
pageTypeAndMode.type === TransactionEditPageType.Transaction && (mode.value === TransactionEditPageMode.Edit || mode.value === TransactionEditPageMode.View) pageTypeAndMode.type === TransactionEditPageType.Transaction && (mode.value === TransactionEditPageMode.Edit || mode.value === TransactionEditPageMode.View)
); );
+8 -9
View File
@@ -205,7 +205,7 @@
<template #media> <template #media>
<div class="display-flex flex-direction-column transaction-date" :style="getTransactionDateStyle(transaction, idx > 0 ? transactionMonthList.items[idx - 1] : undefined)"> <div class="display-flex flex-direction-column transaction-date" :style="getTransactionDateStyle(transaction, idx > 0 ? transactionMonthList.items[idx - 1] : undefined)">
<span class="transaction-day full-line flex-direction-column"> <span class="transaction-day full-line flex-direction-column">
{{ getCalendarDisplayDayOfMonthFromUnixTime(transaction.time) }} {{ transaction.gregorianCalendarDayOfMonth ? numeralSystem.formatNumber(transaction.gregorianCalendarDayOfMonth) : '' }}
</span> </span>
<span class="transaction-day-of-week full-line flex-direction-column" v-if="transaction.displayDayOfWeek"> <span class="transaction-day-of-week full-line flex-direction-column" v-if="transaction.displayDayOfWeek">
{{ getWeekdayShortName(transaction.displayDayOfWeek) }} {{ getWeekdayShortName(transaction.displayDayOfWeek) }}
@@ -265,7 +265,7 @@
</div> </div>
<div class="transaction-footer"> <div class="transaction-footer">
<span>{{ getDisplayTime(transaction) }}</span> <span>{{ getDisplayTime(transaction) }}</span>
<span v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ `(${getDisplayTimezone(transaction)})` }}</span> <span v-if="!isSameAsDefaultTimezoneOffsetMinutes(transaction)">{{ `(${getDisplayTimezone(transaction)})` }}</span>
<span v-if="transaction.sourceAccount">·</span> <span v-if="transaction.sourceAccount">·</span>
<span v-if="transaction.sourceAccount">{{ transaction.sourceAccount.name }}</span> <span v-if="transaction.sourceAccount">{{ transaction.sourceAccount.name }}</span>
<f7-icon class="transaction-account-arrow icon-with-direction" f7="arrow_right" v-if="transaction.sourceAccount && transaction.type === TransactionType.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id"></f7-icon> <f7-icon class="transaction-account-arrow icon-with-direction" f7="arrow_right" v-if="transaction.sourceAccount && transaction.type === TransactionType.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id"></f7-icon>
@@ -624,7 +624,7 @@ import {
DateRangeScene, DateRangeScene,
DateRange DateRange
} from '@/core/datetime.ts'; } from '@/core/datetime.ts';
import { AmountFilterType } from '@/core/numeral.ts'; import { type NumeralSystem, AmountFilterType } from '@/core/numeral.ts';
import { TransactionType } from '@/core/transaction.ts'; import { TransactionType } from '@/core/transaction.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts';
import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts'; import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
@@ -637,8 +637,6 @@ import {
import { import {
getCurrentUnixTime, getCurrentUnixTime,
parseDateTimeFromUnixTime, parseDateTimeFromUnixTime,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
getDayFirstUnixTimeBySpecifiedUnixTime, getDayFirstUnixTimeBySpecifiedUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime, getYearMonthLastUnixTime,
@@ -664,8 +662,8 @@ const props = defineProps<{
const { const {
tt, tt,
getCurrentLanguageTextDirection, getCurrentLanguageTextDirection,
getWeekdayShortName, getCurrentNumeralSystemType,
getCalendarDisplayDayOfMonthFromUnixTime getWeekdayShortName
} = useI18n(); } = useI18n();
const { showAlert, showToast, routeBackOnError } = useI18nUIComponents(); const { showAlert, showToast, routeBackOnError } = useI18nUIComponents();
@@ -676,7 +674,6 @@ const {
customMinDatetime, customMinDatetime,
customMaxDatetime, customMaxDatetime,
currentCalendarDate, currentCalendarDate,
currentTimezoneOffsetMinutes,
firstDayOfWeek, firstDayOfWeek,
fiscalYearStart, fiscalYearStart,
defaultCurrency, defaultCurrency,
@@ -711,6 +708,7 @@ const {
transactionCalendarMaxDate, transactionCalendarMaxDate,
currentMonthTransactionData, currentMonthTransactionData,
hasSubCategoryInQuery, hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
canAddTransaction, canAddTransaction,
getDisplayTime, getDisplayTime,
getDisplayLongYearMonth, getDisplayLongYearMonth,
@@ -737,6 +735,7 @@ const showCustomMonthSheet = ref<boolean>(false);
const showDeleteActionSheet = ref<boolean>(false); const showDeleteActionSheet = ref<boolean>(false);
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection()); const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false); const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
const transactions = computed<TransactionMonthList[]>(() => { const transactions = computed<TransactionMonthList[]>(() => {
@@ -1057,7 +1056,7 @@ function changePageType(type: number): void {
function changeDateFilter(dateType: number): void { function changeDateFilter(dateType: number): void {
if (dateType === DateRange.Custom.type) { // Custom if (dateType === DateRange.Custom.type) { // Custom
if (!query.value.minTime || !query.value.maxTime) { if (!query.value.minTime || !query.value.maxTime) {
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes()); customMaxDatetime.value = getCurrentUnixTime();
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value); customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
} else { } else {
customMaxDatetime.value = query.value.maxTime; customMaxDatetime.value = query.value.maxTime;
+8 -2
View File
@@ -58,6 +58,7 @@ import { TextDirection } from '@/core/text.ts';
import { type TokenInfoResponse, SessionDeviceType, SessionInfo } from '@/models/token.ts'; import { type TokenInfoResponse, SessionDeviceType, SessionInfo } from '@/models/token.ts';
import { isEquals } from '@/lib/common.ts'; import { isEquals } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { parseSessionInfo } from '@/lib/session.ts'; import { parseSessionInfo } from '@/lib/session.ts';
class MobilePageSessionInfo extends SessionInfo { class MobilePageSessionInfo extends SessionInfo {
@@ -69,7 +70,7 @@ class MobilePageSessionInfo extends SessionInfo {
super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.deviceName, sessionInfo.lastSeen); super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.deviceName, sessionInfo.lastSeen);
this.domId = getTokenDomId(sessionInfo.tokenId); this.domId = getTokenDomId(sessionInfo.tokenId);
this.icon = getTokenIcon(sessionInfo.deviceType); this.icon = getTokenIcon(sessionInfo.deviceType);
this.lastSeenDateTime = sessionInfo.lastSeen ? formatUnixTimeToLongDateTime(sessionInfo.lastSeen) : '-'; this.lastSeenDateTime = sessionInfo.lastSeen ? formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(sessionInfo.lastSeen)) : '-';
} }
} }
@@ -77,7 +78,12 @@ const props = defineProps<{
f7router: Router.Router; f7router: Router.Router;
}>(); }>();
const { tt, getCurrentLanguageTextDirection, formatUnixTimeToLongDateTime } = useI18n(); const {
tt,
getCurrentLanguageTextDirection,
formatDateTimeToLongDateTime
} = useI18n();
const { showConfirm, showToast, routeBackOnError } = useI18nUIComponents(); const { showConfirm, showToast, routeBackOnError } = useI18nUIComponents();
const tokensStore = useTokensStore(); const tokensStore = useTokensStore();