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)
}
_, utcOffset, err := c.GetClientTimezone()
clientTimezone, _, err := c.GetClientTimezone()
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
}
@@ -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 {
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
}
_, utcOffset, err := c.GetClientTimezone()
clientTimezone, _, err := c.GetClientTimezone()
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
}
@@ -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 {
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)
}
timezone, _, err := c.GetClientTimezone()
clientTimezone, _, err := c.GetClientTimezone()
if err != nil {
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone offset, because %s", err.Error())
timezone = time.Local
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone, because %s", err.Error())
clientTimezone = time.Local
}
uid := c.GetCurrentUid()
@@ -413,13 +413,13 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
return nil, "", errs.Or(err, errs.ErrOperationFailed)
}
fileName := a.getFileName(user, timezone, fileType)
fileName := a.getFileName(user, clientTimezone, fileType)
return result, fileName, nil
}
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location, fileExtension string) string {
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
func (a *DataManagementsApi) getFileName(user *models.User, clientTimezone *time.Location, fileExtension string) string {
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), clientTimezone)
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
}
clientTimezone, utcOffset, err := c.GetClientTimezone()
clientTimezone, _, err := c.GetClientTimezone()
if err != nil {
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 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{
Type: models.TRANSACTION_TYPE_EXPENSE,
}
@@ -290,7 +290,7 @@ func (a *LargeLanguageModelsApi) parseRecognizedReceiptImageResponse(c *core.Web
if len(recognizedResult.Time) > 0 {
longDateTime := a.getLongDateTime(recognizedResult.Time)
timestamp, err := utils.ParseFromLongDateTime(longDateTime, utcOffset)
timestamp, err := utils.ParseFromLongDateTimeInTimeZone(longDateTime, clientTimezone)
if err != nil {
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
}
_, utcOffset, err := c.GetClientTimezone()
clientTimezone, _, err := c.GetClientTimezone()
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
}
@@ -1688,7 +1688,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
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 {
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
}
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 {
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 (
"bytes"
"time"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
@@ -53,7 +54,7 @@ type alipayTransactionDataCsvFileImporter struct {
}
// 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
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)
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")
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.Equal(t, 4, len(allNewTransactions))
@@ -113,7 +113,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
"------------------------------------------------------------------------------------\n")
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.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -133,7 +133,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin
"------------------------------------------------------------------------------------\n")
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.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 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.Equal(t, 2, len(allNewTransactions))
@@ -202,7 +202,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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)
data2, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
@@ -214,7 +214,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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)
}
@@ -236,7 +236,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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)
}
@@ -259,7 +259,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -275,7 +275,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -291,7 +291,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -308,7 +308,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -325,7 +325,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -342,7 +342,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -359,7 +359,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.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")
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.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")
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.Equal(t, 9, len(allNewTransactions))
@@ -547,7 +547,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -562,7 +562,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
"------------------------------------------------------------------------------------\n")
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.Equal(t, 1, len(allNewTransactions))
@@ -587,7 +587,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipClosedIncomeOrTransferTransa
"2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易关闭 ,\n" +
"------------------------------------------------------------------------------------\n")
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)
}
@@ -608,7 +608,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownProductTransferTransa
"2024-09-01 23:59:59 ,xxxx ,0.05 ,不计收支 ,交易成功 ,\n" +
"------------------------------------------------------------------------------------\n")
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)
}
@@ -629,7 +629,7 @@ func TestAlipayCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *
"2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,xxxx ,\n" +
"------------------------------------------------------------------------------------\n")
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)
}
@@ -648,10 +648,10 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T)
"------------------------------------------------------------------------------------\n")
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)
_, _, _, _, _, _, 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)
}
@@ -672,7 +672,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"金额(元),收/支 ,交易状态 ,\n" +
"0.12 ,收入 ,交易成功 ,\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)
// Missing Amount Column
@@ -683,7 +683,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,收/支 ,交易状态 ,\n" +
"2024-09-01 12:34:56 ,收入 ,交易成功 ,\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)
// Missing Status Column
@@ -694,7 +694,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,金额(元),收/支 ,\n" +
"2024-09-01 12:34:56 ,0.12 ,收入 ,\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)
// Missing Type Column
@@ -705,7 +705,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
"交易创建时间 ,金额(元),交易状态 ,\n" +
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\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)
}
@@ -724,6 +724,6 @@ func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T)
"---------------------------------交易记录明细列表------------------------------------\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)
}
@@ -1,6 +1,8 @@
package beancount
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
@@ -24,7 +26,7 @@ var (
)
// 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)
if err != nil {
@@ -45,5 +47,5 @@ func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, u
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -33,7 +34,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData(t *testi
" Expenses:TestCategory2 1.00 CNY\n"+
"2024-09-04 *\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)
@@ -112,7 +113,7 @@ func TestBeancountTransactionDataFileParseImportedData_MinimumValidData2(t *test
" Assets:TestAccount -1.00 CNY\n"+
"2024-09-04 *\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)
@@ -182,7 +183,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidTime(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024/09/01 *\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)
}
@@ -198,7 +199,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseValidCurrency(t *tes
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"Payee Name\" \"Hello\nWorld\"\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)
@@ -234,13 +235,13 @@ func TestBeancountTransactionDataFileParseImportedData_ParseInvalidAmount(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-01 *\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 *\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)
}
@@ -259,7 +260,7 @@ func TestBeancountTransactionDataFileParseImportedData_ParseDescription(t *testi
" Assets:TestAccount 123.45 CNY\n"+
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\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)
@@ -281,25 +282,25 @@ func TestBeancountTransactionDataFileParseImportedData_InvalidTransaction(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\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)
}
@@ -316,7 +317,7 @@ func TestBeancountTransactionDataFileParseImportedData_NotSupportedToParseSplitT
"2024-09-02 * \"Payee Name\" \"Hello\nWorld\"\n"+
" Assets:TestAccount -0.23 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)
}
@@ -333,27 +334,27 @@ func TestBeancountTransactionDataFileParseImportedData_MissingTransactionRequire
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"* \"narration\"\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)
// Missing Account Name
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\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)
// Missing Amount
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\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)
// Missing Commodity
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"2024-09-01 * \"narration\"\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)
}
@@ -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_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 != "" {
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
@@ -1,6 +1,8 @@
package camt
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
@@ -23,7 +25,7 @@ var (
)
// 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)
if err != nil {
@@ -44,5 +46,5 @@ func (c *camt053TransactionDataImporter) ParseImportedData(ctx core.Context, use
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -65,7 +66,7 @@ func TestCamt053TransactionDataFileParseImportedData_MinimumValidData(t *testing
</Ntry>
</Stmt>
</BkToCstmrStmt>
</Document>`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
</Document>`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err)
@@ -158,7 +159,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseValidTransactionTime(t
</Ntry>
</Stmt>
</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.Equal(t, 3, len(allNewTransactions))
@@ -197,7 +198,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -220,7 +221,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -243,7 +244,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -266,7 +267,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseInvalidTransactionTime
</Ntry>
</Stmt>
</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)
}
@@ -315,7 +316,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry>
</Stmt>
</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.Equal(t, 2, len(allNewTransactions))
@@ -366,7 +367,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry>
</Stmt>
</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.Equal(t, 2, len(allNewTransactions))
@@ -404,7 +405,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry>
</Stmt>
</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.Equal(t, 1, len(allNewTransactions))
@@ -431,7 +432,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionValidAmount
</Ntry>
</Stmt>
</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.Equal(t, 1, len(allNewTransactions))
@@ -468,7 +469,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -499,7 +500,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -530,7 +531,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseTransactionInvalidAmou
</Ntry>
</Stmt>
</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)
}
@@ -573,7 +574,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry>
</Stmt>
</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.Equal(t, 1, len(allNewTransactions))
@@ -608,7 +609,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry>
</Stmt>
</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.Equal(t, 1, len(allNewTransactions))
@@ -635,7 +636,7 @@ func TestCamt053TransactionDataFileParseImportedData_ParseDescription(t *testing
</Ntry>
</Stmt>
</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.Equal(t, 1, len(allNewTransactions))
@@ -665,7 +666,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingAccountNode(t *testi
</Ntry>
</Stmt>
</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)
}
@@ -695,7 +696,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -717,7 +718,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -739,7 +740,7 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry>
</Stmt>
</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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -761,6 +762,6 @@ func TestCamt053TransactionDataFileParseImportedData_MissingTransactionRequiredN
</Ntry>
</Stmt>
</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)
}
@@ -28,10 +28,11 @@ func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context
}
dataRowMap := make(map[datatable.TransactionDataTableColumn]string, 15)
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
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_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(transactionUnixTime, 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_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
dataRowMap[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
@@ -3,6 +3,7 @@ package converter
import (
"sort"
"strings"
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/core"
@@ -29,7 +30,7 @@ type DataTableTransactionDataImporter struct {
}
// 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 {
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
@@ -94,7 +95,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
continue
}
timezoneOffset := defaultTimezoneOffset
timezone := defaultTimezone
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) &&
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
}
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 {
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,
CategoryId: categoryId,
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
TimezoneUtcOffset: timezoneOffset,
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), timezone),
AccountId: account.AccountId,
Amount: amount,
HideAmount: false,
@@ -1,6 +1,8 @@
package converter
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
@@ -14,7 +16,7 @@ type TransactionDataExporter interface {
// TransactionDataImporter defines the structure of transaction data importer
type TransactionDataImporter interface {
// 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
@@ -35,7 +35,7 @@ var (
)
// 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
if err := json.Unmarshal(data, &importRequest); err != nil {
@@ -55,7 +55,7 @@ func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context,
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) {
@@ -75,10 +75,11 @@ func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTabl
}
timezone := time.FixedZone("Transaction Timezone", utcOffset*60)
timezoneOffset := utils.FormatTimezoneOffset(time.Now().Unix(), timezone)
row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns))
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(timezone)
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = timezoneOffset
row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type
row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName
row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName
@@ -1,6 +1,8 @@
package _default
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"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
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(
string(data),
c.columnSeparator,
@@ -104,5 +106,5 @@ func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Co
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 (
"testing"
"time"
"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 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 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)
@@ -207,11 +208,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTim
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -225,7 +226,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidTyp
}
_, _, _, _, _, _, 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)
}
@@ -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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"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)
}
@@ -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"+
"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)
@@ -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"+
"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)
_, _, _, _, _, _, 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 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)
}
@@ -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"+
"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)
_, _, _, _, _, _, 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)
}
@@ -346,11 +347,11 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseInvalidAmo
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -364,14 +365,14 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_ParseNoAmount2(
}
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.Equal(t, int64(12345), allNewTransactions[0].Amount)
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
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.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"+
"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.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"+
"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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
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"+
"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)
_, _, _, _, _, _, 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)
}
@@ -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"+
"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)
@@ -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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -476,7 +477,7 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingFileHead
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)
}
@@ -491,31 +492,31 @@ func TestDefaultTransactionDataCSVFileConverterParseImportedData_MissingRequired
// 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"+
"+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)
// 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"+
"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)
// 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"+
"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)
// 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"+
"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)
// 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"+
"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)
// 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"+
"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)
}
@@ -5,6 +5,7 @@ import (
"encoding/csv"
"io"
"strings"
"time"
"golang.org/x/text/encoding"
"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
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)
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)
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
@@ -2,6 +2,7 @@ package dsv
import (
"testing"
"time"
"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 01:23:45,I,0.12\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)
@@ -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 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 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)
@@ -273,11 +274,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -304,7 +305,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
}
_, _, _, _, _, _, 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)
}
@@ -328,7 +329,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
}
_, _, _, _, _, _, 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)
}
@@ -352,19 +353,19 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
}
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
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(
"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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
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(
"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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
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(
"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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
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.Equal(t, 1, len(allNewTransactions))
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(
"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)
}
@@ -532,11 +533,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -561,11 +562,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -589,7 +590,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *tes
}
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
@@ -615,7 +616,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
}
_, _, _, _, _, _, 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)
}
@@ -639,7 +640,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat
}
_, _, _, _, _, _, 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)
}
@@ -670,7 +671,7 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
"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 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)
@@ -737,7 +738,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
allNewTransactions, allNewAccounts, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"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)
@@ -780,12 +781,12 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"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)
}
@@ -815,11 +816,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -850,7 +851,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
"2024-09-01 00:00:00,B,123.45000000,\n"+
"2024-09-01 01:23:45,I,0.12000000,\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)
@@ -895,7 +896,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// normal space
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)
@@ -904,7 +905,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// no-break space (NBSP)
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)
@@ -913,7 +914,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// narrow no-break space (NNBSP)
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)
@@ -922,7 +923,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingS
// figure space
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)
@@ -954,11 +955,11 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -985,14 +986,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
}
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.Equal(t, int64(12345), allNewTransactions[0].Amount)
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
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.Equal(t, int64(12345), allNewTransactions[0].Amount)
@@ -1020,7 +1021,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
}
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.Equal(t, 1, len(allNewTransactions))
@@ -1049,14 +1050,14 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
}
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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
_, _, _, _, _, _, 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)
}
@@ -1081,7 +1082,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
}
_, _, _, _, _, 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)
@@ -1121,7 +1122,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
}
_, _, _, _, _, 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)
@@ -1152,7 +1153,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
}
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.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())
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 (
"bytes"
"strings"
"time"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
@@ -61,7 +62,7 @@ var (
)
// 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()
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
@@ -97,7 +98,7 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx c
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) {
@@ -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 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 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)
@@ -122,7 +122,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseOutstandingBalanceMo
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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)
@@ -171,12 +171,12 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidTime(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
}
@@ -191,7 +191,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
}
@@ -208,7 +208,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\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 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)
@@ -237,14 +237,14 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountCurren
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\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 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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"账户币种\",\"金额\",\"备注\",\"关联Id\"\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 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)
}
@@ -259,19 +259,19 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseNotSupportedCurrency
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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 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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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 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)
}
@@ -286,24 +286,24 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAmount(t *tes
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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 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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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 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)
}
@@ -319,7 +319,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseDescription(t *testi
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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.Equal(t, 1, len(allNewTransactions))
@@ -337,7 +337,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *
allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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.Equal(t, 1, len(allNewTransactions))
@@ -345,7 +345,7 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_WithAdditionalOptions(t *
allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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.Equal(t, 1, len(allNewTransactions))
@@ -366,18 +366,18 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_InvalidRelatedId(t *testi
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\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)
}
@@ -390,10 +390,10 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingFileHeader(t *test
DefaultCurrency: "CNY",
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -409,36 +409,36 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_MissingRequiredColumn(t *
// Missing Time Column
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
// Missing Type Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
// Missing Sub Category Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
// Missing Account Name Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
// Missing Amount Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
// Missing Related ID Column
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\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)
}
@@ -1,6 +1,8 @@
package feidee
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"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
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)
if err != nil {
@@ -45,5 +47,5 @@ func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
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")
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.Equal(t, 7, len(allNewTransactions))
@@ -1,6 +1,8 @@
package feidee
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"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
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)
if err != nil {
@@ -44,5 +46,5 @@ func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx c
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
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")
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.Equal(t, 7, len(allNewTransactions))
@@ -2,6 +2,7 @@ package fireflyIII
import (
"bytes"
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"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
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)
dataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, true)
@@ -52,5 +53,5 @@ func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Co
transactionDataTable := datatable.CreateNewTransactionDataTableFromBasicDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
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 (
"testing"
"time"
"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"+
"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"+
"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)
@@ -93,11 +94,11 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidTime(t *testing.
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -111,7 +112,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidType(t *testing.
}
_, _, _, _, _, _, 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)
}
@@ -125,14 +126,14 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseAccountNameAsCategoryNa
}
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.Equal(t, 1, len(allNewTransactions))
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"+
"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.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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"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.Equal(t, 1, len(allNewTransactions))
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"+
"\"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)
@@ -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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -215,7 +216,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCu
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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -225,7 +226,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseValidForeignAmountAndCu
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"+
"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.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"+
"\"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)
_, _, _, _, _, _, 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"+
"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)
}
@@ -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"+
"\"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)
_, _, _, _, _, _, 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)
}
@@ -281,11 +282,11 @@ func TestFireFlyIIICsvFileimporterParseImportedData_ParseInvalidAmount(t *testin
}
_, _, _, _, _, _, 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)
_, _, _, _, _, _, 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)
}
@@ -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"+
"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.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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -338,7 +339,7 @@ func TestFireFlyIIICsvFileimporterParseImportedData_MissingFileHeader(t *testing
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)
}
@@ -353,31 +354,31 @@ func TestFireFlyIIICsvFileimporterParseImportedData_MissingRequiredColumn(t *tes
// Missing Time Column
_, _, _, _, _, _, 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)
// Missing Type Column
_, _, _, _, _, _, 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)
// Missing Sub Category Column
_, _, _, _, _, _, 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)
// Missing Account Name Column
_, _, _, _, _, _, 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)
// Missing Amount Column
_, _, _, _, _, _, 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)
// Missing Account2 Name Column
_, _, _, _, _, _, 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)
}
@@ -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_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
}
// trim trailing zero in decimal
@@ -1,6 +1,8 @@
package gnucash
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
@@ -24,7 +26,7 @@ var (
)
// 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)
if err != nil {
@@ -45,5 +47,5 @@ func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, use
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"
"compress/gzip"
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -194,7 +195,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidData(t *tes
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)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -218,7 +219,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_GzippedMinimumValidData
assert.Nil(t, err)
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)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -357,7 +358,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MinimumValidDataWithRev
" </trn:splits>\n"+
"</gnc:transaction>\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)
checkParsedMinimumValidData(t, allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags)
@@ -389,7 +390,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
" </trn:split>\n"+
" </trn:splits>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -409,7 +410,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidTime(t *tes
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -439,7 +440,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
" </trn:split>\n"+
" </trn:splits>\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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725230096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -461,7 +462,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidTimezone(t *t
" </trn:split>\n"+
" </trn:splits>\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.Equal(t, 1, len(allNewTransactions))
assert.Equal(t, int64(1725148196), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
@@ -558,7 +559,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAccountCurren
" </trn:splits>\n"+
"</gnc:transaction>\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)
@@ -600,7 +601,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
" </trn:split>\n"+
" </trn:splits>\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.Equal(t, 1, len(allNewTransactions))
@@ -623,7 +624,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseValidAmount(t *tes
" </trn:split>\n"+
" </trn:splits>\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.Equal(t, 1, len(allNewTransactions))
@@ -656,7 +657,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+
" </trn:splits>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -676,7 +677,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+
" </trn:splits>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -696,7 +697,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseInvalidAmount(t *t
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -727,7 +728,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_ParseDescription(t *tes
" </trn:split>\n"+
" </trn:splits>\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.Equal(t, 1, len(allNewTransactions))
@@ -756,7 +757,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_SkipZeroAmountTransacti
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -806,7 +807,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_NotSupportedToParseSpli
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -873,7 +874,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingAccountRequiredN
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -901,7 +902,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+
" </trn:splits>\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)
// Missing Transaction Splits Node
@@ -912,7 +913,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" <ts:date>2024-09-01 00:00:00 +0000</ts:date>\n"+
" </trn:date-posted>\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)
// Missing Transaction Split Quantity Node
@@ -931,7 +932,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+
" </trn:splits>\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)
// Missing Transaction Split Account Node
@@ -951,7 +952,7 @@ func TestGnuCashTransactionDatabaseFileParseImportedData_MissingTransactionRequi
" </trn:split>\n"+
" </trn:splits>\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)
}
@@ -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_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Unix(), dateTime.Location())
if len(gnucashTransaction.Splits) == 2 {
splitData1 := gnucashTransaction.Splits[0]
@@ -1,6 +1,8 @@
package iif
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
@@ -23,7 +25,7 @@ var (
)
// 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)
accountDatasets, transactionDatasets, err := iifDataReader.read(ctx)
@@ -39,5 +41,5 @@ func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, use
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -50,7 +51,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
"ENDTRNS\t\t\t\t\n"+
"TRNS\tCREDIT CARD\t09/07/2024\tTest Category2\t34.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)
@@ -147,7 +148,7 @@ func TestIIFTransactionDataFileParseImportedData_MinimumValidDataWithoutAccountD
"!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.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)
@@ -203,7 +204,7 @@ func TestIIFTransactionDataFileParseImportedData_MultipleDataset(t *testing.T) {
"ENDTRNS\t\t\t\t\n"+
"!ACCNT\tTEST\tNAME\tACCNTTYPE\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)
@@ -268,7 +269,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseCategoryAndSubCategory(t *
"ENDTRNS\t\t\t\n"+
"TRNS\t09/02/2024\tTest Account2\t-123.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)
@@ -323,7 +324,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseYearMonthDayFormatTime(t *
"ENDTRNS\t\t\t\n"+
"TRNS\t2024/9/4\tTest Account\t123.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)
@@ -355,7 +356,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayYearFormatTim
"ENDTRNS\t\t\t\n"+
"TRNS\t9/3/2024\tTest Account\t123.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)
@@ -386,7 +387,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseShortMonthDayTwoDigitsYear
"ENDTRNS\t\t\t\n"+
"TRNS\t24/9/3\tTest Account\t123.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)
@@ -411,7 +412,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+
"TRNS\t09-01-2024\tTest Account\t123.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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -420,7 +421,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+
"TRNS\t2024-09-01\tTest Account\t123.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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -429,7 +430,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!ENDTRNS\t\t\t\n"+
"TRNS\t9/24\tTest Account\t123.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)
}
@@ -448,7 +449,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
"!ENDTRNS\t\t\t\n"+
"TRNS\t9/01/2024\tTest Account\t123,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)
@@ -471,7 +472,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123 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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -480,7 +481,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\t123.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)
}
@@ -499,7 +500,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"!ENDTRNS\t\t\t\t\t\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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -511,7 +512,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"!ENDTRNS\t\t\t\t\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"+
"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.Equal(t, 1, len(allNewTransactions))
@@ -553,7 +554,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testin
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+
"SPL\t09/05/2024\tTest Account3\t-40.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)
@@ -667,7 +668,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescriptio
"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 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.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 Account3\tfoo\t-12.34\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.Equal(t, 3, len(allNewTransactions))
@@ -708,7 +709,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"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 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)
// Transaction with invalid amount
@@ -719,7 +720,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\t09/01/2024\tTest Account\t123 45\n"+
"SPL\t09/01/2024\tTest Account2\t-100.00\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)
// Transaction split data with invalid amount
@@ -730,7 +731,7 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-100 00\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)
// 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"+
"SPL\t09/01/2024\tTest Account2\t-100.00\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)
}
@@ -760,7 +761,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\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)
// Missing Transaction And Split Line
@@ -768,7 +769,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
"!SPL\tDATE\tACCNT\tAMOUNT\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)
// Missing Split Line
@@ -777,7 +778,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\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)
// Missing Transaction End Line
@@ -786,7 +787,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
"!ENDTRNS\t\t\t\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)
// 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"+
"SPL\t09/01/2024\tTest Account2\t-123.45\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)
// Invalid Line
@@ -808,7 +809,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\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)
// 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"+
"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)
// Repeat Transaction End Line
@@ -831,7 +832,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
"SPL\t09/01/2024\tTest Account2\t-123.45\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)
}
@@ -848,25 +849,25 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"TRNS\t09/01/2024\tTest Account\t123.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)
// Missing Transaction Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!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)
// Missing Split Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!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)
// Missing Transaction End Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!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)
// Missing Transaction End Sample Line (following is data line)
@@ -875,14 +876,14 @@ func TestIIFTransactionDataFileParseImportedData_InvalidHeaderLines(t *testing.T
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
"TRNS\t09/01/2024\tTest Account\t123.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)
// Invalid Sample Line
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!TRNS\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)
}
@@ -902,7 +903,7 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+
"TRNS\tTest Account\t123.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)
// Missing Account Column
@@ -912,7 +913,7 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\t123.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)
// Missing Amount Column
@@ -922,6 +923,6 @@ func TestIIFTransactionDataFileParseImportedData_MissingRequiredColumn(t *testin
"!ENDTRNS\t\t\t\n"+
"TRNS\t09/01/2024\tTest Account\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)
}
@@ -2,6 +2,7 @@ package jdcom
import (
"bytes"
"time"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
@@ -27,7 +28,7 @@ var (
)
// 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()
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)
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 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\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.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-02 01:23:45,xxx,xxx,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.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -154,7 +154,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
data2 := "导出信息:\n" +
@@ -163,7 +163,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
}
@@ -182,7 +182,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
}
@@ -201,7 +201,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *test
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
}
@@ -221,7 +221,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\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.Equal(t, 1, len(allNewTransactions))
@@ -236,7 +236,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\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.Equal(t, 1, len(allNewTransactions))
@@ -253,7 +253,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n"
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.Equal(t, 1, len(allNewTransactions))
@@ -270,7 +270,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n"
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.Equal(t, 1, len(allNewTransactions))
@@ -287,7 +287,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n"
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.Equal(t, 1, len(allNewTransactions))
@@ -303,7 +303,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testin
"2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n"
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.Equal(t, 1, len(allNewTransactions))
@@ -327,7 +327,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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.Equal(t, 1, len(allNewTransactions))
@@ -339,7 +339,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" +
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\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, "foo\"bar,\ntest", allNewTransactions[0].Comment)
@@ -349,7 +349,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testin
"\n" +
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\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, "Test", allNewTransactions[0].Comment)
}
@@ -369,7 +369,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransacti
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
}
@@ -388,7 +388,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTra
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
}
@@ -403,10 +403,10 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testi
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
_, _, _, _, _, _, 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)
}
@@ -426,7 +426,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\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)
// Missing Merchant Name Column
@@ -436,7 +436,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\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)
// Missing Transaction Memo Column
@@ -446,7 +446,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\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)
// Missing Amount Column
@@ -456,7 +456,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\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)
// Missing Related Account Column
@@ -466,7 +466,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,商户名称,交易说明,金额,交易状态,收/支\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)
// Missing Status Column
@@ -476,7 +476,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\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)
// Missing Type Column
@@ -486,7 +486,7 @@ func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *t
"\n" +
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\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)
}
@@ -504,6 +504,6 @@ func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testi
"日期区间:2025-01-01 至 2025-09-01\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)
}
@@ -1,6 +1,8 @@
package mt
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
@@ -22,7 +24,7 @@ var (
)
// 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)
mt940Data, err := mt940DataReader.read(ctx)
@@ -38,5 +40,5 @@ func (c *mt940TransactionDataFileImporter) ParseImportedData(ctx core.Context, u
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -32,7 +33,7 @@ func TestMT940TransactionDataFileParseImportedData_MinimumValidData(t *testing.T
:61:2506020603D234,56NTRFFOOBAR
:86:Transaction 2
:62F:C250601CNY123,45
-}`), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
-}`), time.UTC, converter.DefaultImporterOptions, nil, nil, nil, nil, nil)
assert.Nil(t, err)
@@ -92,7 +93,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionValidAmountAn
:61:250603C1,NTRFTEST
:86:Transaction 3
: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.Equal(t, 3, len(allNewTransactions))
@@ -118,7 +119,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
:60F:C250601CNY123,45
:61:2506010602C123 45NTRFTEST
: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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -129,7 +130,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionInvalidAmount
:60F:C250601CNY123,45
:61:2506010602C12.45NTRFTEST
: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)
}
@@ -157,7 +158,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseTransactionType(t *testi
:61:250604RD123,45NTRFTEST
:86:Transaction 4
: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.Equal(t, 4, len(allNewTransactions))
@@ -187,7 +188,7 @@ func TestMT940TransactionDataFileParseImportedData_ParseDescription(t *testing.T
Part 2
Part 3
: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.Equal(t, 1, len(allNewTransactions))
@@ -210,6 +211,6 @@ func TestMT940TransactionDataFileParseImportedData_MissingRequiredField(t *testi
:28C:123/1
:61:250601C123,45NTRFTEST
: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)
}
@@ -1,6 +1,8 @@
package ofx
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"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
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)
if err != nil {
@@ -44,5 +46,5 @@ func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -77,7 +78,7 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
" </CCSTMTRS>\n"+
" </CCSTMTTRNRS>\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)
@@ -211,7 +212,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) {
" </CCSTMTRS>\n"+
" </CCSTMTTRNRS>\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)
@@ -296,7 +297,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te
" </STMTRS>\n"+
" </STMTTRNRS>\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)
@@ -338,7 +339,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+
" </STMTTRNRS>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -360,7 +361,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+
" </STMTTRNRS>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -382,7 +383,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+
" </STMTTRNRS>\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)
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
@@ -404,7 +405,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t *
" </STMTRS>\n"+
" </STMTTRNRS>\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)
}
@@ -436,7 +437,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint
" </STMTRS>\n"+
" </STMTTRNRS>\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)
@@ -472,7 +473,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
" </STMTRS>\n"+
" </STMTTRNRS>\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)
}
@@ -505,7 +506,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes
" </STMTRS>\n"+
" </STMTTRNRS>\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.Equal(t, 1, len(allNewTransactions))
@@ -542,7 +543,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+
" </STMTTRNRS>\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.Equal(t, 1, len(allNewTransactions))
@@ -568,7 +569,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+
" </STMTTRNRS>\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.Equal(t, 1, len(allNewTransactions))
@@ -596,7 +597,7 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
" </STMTRS>\n"+
" </STMTTRNRS>\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.Equal(t, 1, len(allNewTransactions))
@@ -629,7 +630,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi
" </STMTRS>\n"+
" </STMTTRNRS>\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)
}
@@ -661,7 +662,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing.
" </STMTRS>\n"+
" </STMTTRNRS>\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)
}
@@ -693,7 +694,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+
" </STMTTRNRS>\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)
// Missing Transaction Type Node
@@ -715,7 +716,7 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+
" </STMTTRNRS>\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)
// Missing Amount Node
@@ -737,6 +738,6 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode(
" </STMTRS>\n"+
" </STMTTRNRS>\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)
}
@@ -1,6 +1,8 @@
package qif
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/core"
"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
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)
qifData, err := qifDataReader.read(ctx)
@@ -51,5 +53,5 @@ func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *m
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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -43,7 +44,7 @@ func TestQIFTransactionDataFileParseImportedData_MinimumValidData(t *testing.T)
"D2024-09-05\n"+
"T0.06\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)
@@ -138,7 +139,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseYearMonthDayDateFormatTime
"^\n"+
"D2024'9.5\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)
@@ -176,7 +177,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseMonthDayYearDateFormatTime
"^\n"+
"D9.5'2024\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)
@@ -214,7 +215,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDayYearMonthDateFormatTime
"^\n"+
"D5'9.2024\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)
@@ -252,7 +253,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseShortYearMonthDayDateForma
"^\n"+
"D24'9.5\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)
@@ -278,7 +279,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidTime(t *testing.T)
"!Type:Bank\n"+
"D2024 09 01\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)
}
@@ -295,7 +296,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithThousandsSeparat
"!Type:Bank\n"+
"D2024-09-01\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)
@@ -317,7 +318,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T
"!Type:Bank\n"+
"D2024-09-01\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)
}
@@ -334,7 +335,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Cash\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -347,7 +348,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:CCard\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -360,7 +361,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Oth A\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -373,7 +374,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccountType(t *testing.T)
"!Type:Oth L\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -399,7 +400,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAccount(t *testing.T) {
"!Type:Bank\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -425,7 +426,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseAmountWithLeadingPlusSign(
"!Type:Bank\n"+
"D2024-09-01\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.Equal(t, 1, len(allNewTransactions))
@@ -446,7 +447,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseSubCategory(t *testing.T)
"D2024-09-01\n"+
"T-123.45\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.Equal(t, 1, len(allNewTransactions))
@@ -477,7 +478,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"D2024-09-02\n"+
"T-234.56\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.Equal(t, 2, len(allNewTransactions))
@@ -494,7 +495,7 @@ func TestQIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
"D2024-09-02\n"+
"T-234.56\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.Equal(t, 2, len(allNewTransactions))
@@ -516,7 +517,7 @@ func TestQIFTransactionDataFileParseImportedData_WithAdditionalOptions(t *testin
"D2024-09-01\n"+
"T-123.45\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.Equal(t, 1, len(allNewTransactions))
@@ -527,7 +528,7 @@ func TestQIFTransactionDataFileParseImportedData_WithAdditionalOptions(t *testin
"D2024-09-01\n"+
"T-123.45\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.Equal(t, 1, len(allNewTransactions))
@@ -548,13 +549,13 @@ func TestQIFTransactionDataFileParseImportedData_MissingRequiredFields(t *testin
_, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte(
"!Type:Bank\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)
// Missing Amount Field
_, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte(
"!Type:Bank\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)
}
@@ -2,6 +2,7 @@ package wechat
import (
"bytes"
"time"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
@@ -27,7 +28,7 @@ var (
)
// 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()
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)
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 23:59:59,零钱充值,/,¥0.05,充值完成\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.Equal(t, 4, len(allNewTransactions))
@@ -109,7 +109,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseRefundTransaction(t *tes
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
@@ -136,7 +136,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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)
data2 := "微信支付账单明细,,,,\n" +
@@ -146,7 +146,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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)
}
@@ -166,7 +166,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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)
}
@@ -186,7 +186,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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)
}
@@ -207,7 +207,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),支付方式,当前状态\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.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"
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.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"
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.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"
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.Equal(t, 1, len(allNewTransactions))
@@ -273,7 +273,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T
"2024-09-03 23:59:59,信用卡还款,/,¥0.01,零钱,支付成功\n"
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.Equal(t, 1, len(allNewTransactions))
@@ -297,7 +297,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态,备注\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.Equal(t, 1, len(allNewTransactions))
@@ -310,7 +310,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\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, "foo\"bar,\ntest", allNewTransactions[0].Comment)
@@ -321,7 +321,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_ParseDescription(t *testing.T
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,商品,收/支,金额(元),当前状态,备注\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, "Test", allNewTransactions[0].Comment)
}
@@ -342,7 +342,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_SkipUnknownTransferTransactio
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元),当前状态\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)
}
@@ -357,10 +357,10 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.
data := "交易时间,交易类型,收/支,金额(元),当前状态\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)
_, _, _, _, _, _, 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)
}
@@ -381,7 +381,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易类型,收/支,金额(元),当前状态\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)
// Missing Category Column
@@ -392,7 +392,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,收/支,金额(元),当前状态\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)
// Missing Type Column
@@ -403,7 +403,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,金额(元),当前状态\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)
// Missing Amount Column
@@ -414,7 +414,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,当前状态\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)
// Missing Status Column
@@ -425,7 +425,7 @@ func TestWeChatPayCsvFileImporterParseImportedData_MissingRequiredColumn(t *test
"----------------------微信支付账单明细列表--------------------,,,,\n" +
"交易时间,交易类型,收/支,金额(元)\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)
}
@@ -444,6 +444,6 @@ func TestWeChatPayCsvFileImporterParseImportedData_NoTransactionData(t *testing.
",,,,\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)
}
@@ -1,6 +1,8 @@
package wechat
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"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
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)
if err != nil {
@@ -49,5 +51,5 @@ func (c *wechatPayTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Co
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, wechatPayTransactionSupportedColumns, transactionRowParser)
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,
CategoryId: categoryId,
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Location()),
TimezoneUtcOffset: utils.GetTimezoneOffsetMinutes(transactionTime.Unix(), transactionTime.Location()),
AccountId: sourceAccountId,
Amount: amount,
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
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 {
return errs.ErrUserIdInvalid
}
@@ -266,11 +266,14 @@ func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Acco
}
transactionTime := defaultTransactionTime
transactionUtcOffset := utils.GetTimezoneOffsetMinutes(now, clientTimezone)
if i == 0 && mainAccountBalanceTime > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(mainAccountBalanceTime)
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(mainAccountBalanceTime, clientTimezone)
} else if i > 0 && len(childrenAccountBalanceTimes) > i-1 && childrenAccountBalanceTimes[i-1] > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(childrenAccountBalanceTimes[i-1])
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(childrenAccountBalanceTimes[i-1], clientTimezone)
} else {
defaultTransactionTime++
}
@@ -281,7 +284,7 @@ func (s *AccountService) CreateAccounts(c core.Context, mainAccount *models.Acco
Deleted: false,
Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
TransactionTime: transactionTime,
TimezoneUtcOffset: utcOffset,
TimezoneUtcOffset: transactionUtcOffset,
AccountId: allAccounts[i].AccountId,
Amount: allAccounts[i].Balance,
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
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 {
return errs.ErrUserIdInvalid
}
@@ -407,9 +410,11 @@ func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Acco
}
transactionTime := defaultTransactionTime
transactionUtcOffset := utils.GetTimezoneOffsetMinutes(now, clientTimezone)
if len(addSubAccountBalanceTimes) > i && addSubAccountBalanceTimes[i] > 0 {
transactionTime = utils.GetMinTransactionTimeFromUnixTime(addSubAccountBalanceTimes[i])
transactionUtcOffset = utils.GetTimezoneOffsetMinutes(addSubAccountBalanceTimes[i], clientTimezone)
} else {
defaultTransactionTime++
}
@@ -420,7 +425,7 @@ func (s *AccountService) ModifyAccounts(c core.Context, mainAccount *models.Acco
Deleted: false,
Type: models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
TransactionTime: transactionTime,
TimezoneUtcOffset: utcOffset,
TimezoneUtcOffset: transactionUtcOffset,
AccountId: childAccount.AccountId,
Amount: childAccount.Balance,
RelatedAccountId: childAccount.AccountId,
+15 -10
View File
@@ -224,12 +224,17 @@ func ParseFromLongDateTimeToMaxUnixTime(t string) (time.Time, error) {
return time.ParseInLocation(longDateTimeFormat, t, timezone)
}
// ParseFromLongDateTime parses a formatted string in long date time format
func ParseFromLongDateTime(t string, utcOffset int16) (time.Time, error) {
// ParseFromLongDateTimeInFixedUtcOffset parses a formatted string in long date time format
func ParseFromLongDateTimeInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
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
func ParseFromLongDateTimeWithTimezone(t string) (time.Time, error) {
return time.Parse(longDateTimeWithTimezoneFormat, t)
@@ -245,14 +250,14 @@ func ParseFromLongDateTimeWithTimezoneRFC3339Format(t string) (time.Time, error)
return time.Parse(longDateTimeWithTimezoneRFC3339Format, t)
}
// ParseFromLongDateTimeWithoutSecond parses a formatted string in long date time format (no second)
func ParseFromLongDateTimeWithoutSecond(t string, utcOffset int16) (time.Time, error) {
// ParseFromLongDateTimeWithoutSecondInFixedUtcOffset parses a formatted string in long date time format (no second) with fixed UTC offset
func ParseFromLongDateTimeWithoutSecondInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
return time.ParseInLocation(longDateTimeWithoutSecondFormat, t, timezone)
}
// ParseFromShortDateTime parses a formatted string in short date time format
func ParseFromShortDateTime(t string, utcOffset int16) (time.Time, error) {
// ParseFromShortDateTimeInFixedUtcOffset parses a formatted string in short date time format with fixed UTC offset
func ParseFromShortDateTimeInFixedUtcOffset(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
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
func GetTimezoneOffsetMinutes(timezone *time.Location) int16 {
_, tzOffset := time.Now().In(timezone).Zone()
func GetTimezoneOffsetMinutes(unixTime int64, timezone *time.Location) int16 {
_, tzOffset := parseFromUnixTime(unixTime).In(timezone).Zone()
tzMinuteOffset := int16(tzOffset / 60)
return tzMinuteOffset
@@ -298,8 +303,8 @@ func GetServerTimezoneOffsetMinutes() int16 {
}
// FormatTimezoneOffset returns "+/-HH:MM" format of timezone
func FormatTimezoneOffset(timezone *time.Location) string {
tzMinutesOffset := GetTimezoneOffsetMinutes(timezone)
func FormatTimezoneOffset(unixTime int64, timezone *time.Location) string {
tzMinutesOffset := GetTimezoneOffsetMinutes(unixTime, timezone)
sign := "+"
hourAbsOffset := tzMinutesOffset / 60
+68 -17
View File
@@ -215,15 +215,36 @@ func TestParseFromLongDateTimeToMaxUnixTime(t *testing.T) {
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromLongDateTime(t *testing.T) {
func TestParseFromLongDateTimeInFixedUtcOffset(t *testing.T) {
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)
actualValue := actualTime.Unix()
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) {
expectedValue := int64(1617238883)
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)
}
func TestParseFromLongDateTimeWithoutSecond(t *testing.T) {
func TestParseFromLongDateTimeWithoutSecondInFixedUtcOffset(t *testing.T) {
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)
actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromShortDateTime(t *testing.T) {
func TestParseFromShortDateTimeInFixedUtcOffset(t *testing.T) {
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)
actualValue := actualTime.Unix()
@@ -312,52 +333,82 @@ func TestIsUnixTimeEqualsYearAndMonth(t *testing.T) {
assert.Equal(t, false, actualValue)
}
func TestGetTimezoneOffsetMinutes(t *testing.T) {
func TestGetTimezoneOffsetMinutes_FixedTimezone(t *testing.T) {
timezone := time.FixedZone("Test Timezone", 120*60)
expectedValue := int16(120)
actualValue := GetTimezoneOffsetMinutes(timezone)
actualValue := GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 345*60)
expectedValue = int16(345)
actualValue = GetTimezoneOffsetMinutes(timezone)
actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -720*60)
expectedValue = int16(-720)
actualValue = GetTimezoneOffsetMinutes(timezone)
actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 0)
expectedValue = int16(0)
actualValue = GetTimezoneOffsetMinutes(timezone)
actualValue = GetTimezoneOffsetMinutes(time.Now().Unix(), timezone)
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)
expectedValue := "+02:00"
actualValue := FormatTimezoneOffset(timezone)
actualValue := FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 345*60)
expectedValue = "+05:45"
actualValue = FormatTimezoneOffset(timezone)
actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -720*60)
expectedValue = "-12:00"
actualValue = FormatTimezoneOffset(timezone)
actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", -150*60)
expectedValue = "-02:30"
actualValue = FormatTimezoneOffset(timezone)
actualValue = FormatTimezoneOffset(time.Now().Unix(), timezone)
assert.Equal(t, expectedValue, actualValue)
timezone = time.FixedZone("Test Timezone", 0)
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)
}
@@ -17,6 +17,7 @@ import type { TransactionReconciliationStatementResponseItem } from '@/models/tr
import { isArray } from '@/lib/common.ts';
import { sumAmounts } from '@/lib/numeral.ts';
import {
parseDateTimeFromUnixTime,
getGregorianCalendarYearAndMonthFromUnixTime,
getYearFirstUnixTimeBySpecifiedUnixTime,
getQuarterFirstUnixTimeBySpecifiedUnixTime,
@@ -52,11 +53,11 @@ export interface CommonAccountBalanceTrendsChartProps {
export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) {
const {
formatUnixTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth,
formatUnixTimeToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear
formatDateTimeToShortDate,
formatDateTimeToGregorianLikeShortYear,
formatDateTimeToGregorianLikeShortYearMonth,
formatDateTimeToGregorianLikeYearQuarter,
formatDateTimeToGregorianLikeFiscalYear
} = useI18n();
const dataDateRange = computed<AccountBalanceUnixTimeAndBalanceRange | null>(() => {
@@ -150,19 +151,20 @@ export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTren
for (const dateRange of allDateRanges.value) {
const dataItems = dayDataItemsMap[dateRange.minUnixTime];
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
let displayDate = '';
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
displayDate = formatUnixTimeToGregorianLikeShortYear(dateRange.minUnixTime);
displayDate = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
displayDate = formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime);
displayDate = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
displayDate = formatUnixTimeToGregorianLikeYearQuarter(dateRange.minUnixTime);
displayDate = formatDateTimeToGregorianLikeYearQuarter(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
displayDate = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime);
displayDate = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
displayDate = formatUnixTimeToShortDate(dateRange.minUnixTime);
displayDate = formatDateTimeToShortDate(minDateTime);
} else {
return ret;
}
+31 -19
View File
@@ -1,16 +1,23 @@
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 {
getCurrentUnixTime,
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getTodayFirstUnixTime,
getDummyUnixTimeForLocalUsage,
getActualUnixTimeForStore,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getSameDateTimeWithCurrentTimezone,
getSameDateTimeWithBrowserTimezone,
parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithBrowserTimezone,
getDateRangeByDateType
} from '@/lib/datetime.ts';
@@ -45,23 +52,21 @@ function getDateRangeFromProps(props: CommonDateRangeSelectionProps): { minDate:
}
export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps) {
const { tt, formatUnixTimeToLongDateTime } = useI18n();
const { tt, formatDateTimeToLongDateTime } = useI18n();
const userStore = useUserStore();
const { minDate, maxDate } = getDateRangeFromProps(props);
const dateRange = ref<Date[]>([
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(minDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(maxDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
getLocalDatetimeFromSameDateTimeOfUnixTime(minDate),
getLocalDatetimeFromSameDateTimeOfUnixTime(maxDate)
]);
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const beginDateTime = computed<string>(() => {
const actualBeginUnixTime = getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(dateRange.value[0] as Date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return formatUnixTimeToLongDateTime(actualBeginUnixTime);
return formatDateTimeToLongDateTime(getDateTimeFromSameDateTimeOfLocalDatetime(dateRange.value[0] as Date));
});
const endDateTime = computed<string>(() => {
const actualEndUnixTime = getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(dateRange.value[1] as Date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return formatUnixTimeToLongDateTime(actualEndUnixTime);
return formatDateTimeToLongDateTime(getDateTimeFromSameDateTimeOfLocalDatetime(dateRange.value[1] as Date));
});
const presetRanges = computed<PresetDateRange[]>(() => {
const presetRanges:PresetDateRange[] = [];
@@ -82,8 +87,8 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
presetRanges.push({
label: tt(dateRangeType.name),
value: [
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
getLocalDatetimeFromSameDateTimeOfUnixTime(dateRange.minTime),
getLocalDatetimeFromSameDateTimeOfUnixTime(dateRange.maxTime)
]
});
});
@@ -91,6 +96,14 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
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 {
if (!dateRange.value[0] || !dateRange.value[1]) {
return null;
@@ -99,16 +112,13 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
const currentMinDate = dateRange.value[0];
const currentMaxDate = dateRange.value[1];
let minUnixTime = getUnixTimeFromLocalDatetime(currentMinDate);
let maxUnixTime = getUnixTimeFromLocalDatetime(currentMaxDate);
const minUnixTime = getDateTimeFromSameDateTimeOfLocalDatetime(currentMinDate).getUnixTime();
const maxUnixTime = getDateTimeFromSameDateTimeOfLocalDatetime(currentMaxDate).getUnixTime();
if (minUnixTime < 0 || maxUnixTime < 0) {
throw new Error('Date is too early');
}
minUnixTime = getActualUnixTimeForStore(minUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
maxUnixTime = getActualUnixTimeForStore(maxUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return {
minUnixTime,
maxUnixTime
@@ -123,6 +133,8 @@ export function useDateRangeSelectionBase(props: CommonDateRangeSelectionProps)
endDateTime,
presetRanges,
// functions
getLocalDatetimeFromSameDateTimeOfUnixTime,
getDateTimeFromSameDateTimeOfLocalDatetime,
getFinalDateRange
};
}
@@ -5,6 +5,15 @@ import { useI18n } from '@/locales/helpers.ts';
import { type NameValue } from '@/core/base.ts';
import { NumeralSystem } from '@/core/numeral.ts';
import {
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getSameDateTimeWithBrowserTimezone,
getSameDateTimeWithTimezoneOffset,
parseDateTimeFromUnixTimeWithBrowserTimezone,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
export interface TimePickerValue {
value: string;
itemsIndex: number;
@@ -30,6 +39,14 @@ export function useDateTimeSelectionBase() {
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
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 {
let textualValue = value.toString();
@@ -89,6 +106,8 @@ export function useDateTimeSelectionBase() {
// computed
meridiemItems,
// functions
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue,
generateAllHours,
generateAllMinutesOrSeconds
@@ -7,6 +7,7 @@ import {
getYear0BasedMonthObjectFromString,
getYearMonthStringFromYear0BasedMonthObject,
getCurrentUnixTime,
parseDateTimeFromUnixTime,
getThisYearFirstUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime
@@ -49,7 +50,7 @@ function getMonthRangeFromProps(props: CommonMonthRangeSelectionProps): { minDat
}
export function useMonthRangeSelectionBase(props: CommonMonthRangeSelectionProps) {
const { formatUnixTimeToGregorianLikeLongYearMonth } = useI18n();
const { formatDateTimeToGregorianLikeLongYearMonth } = useI18n();
const { minDate, maxDate } = getMonthRangeFromProps(props);
const dateRange = ref<Year0BasedMonth[]>([
@@ -57,8 +58,8 @@ export function useMonthRangeSelectionBase(props: CommonMonthRangeSelectionProps
maxDate
]);
const beginDateTime = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(dateRange.value[0] as Year0BasedMonth)));
const endDateTime = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthLastUnixTime(dateRange.value[1] as Year0BasedMonth)));
const beginDateTime = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthFirstUnixTime(dateRange.value[0] as Year0BasedMonth))));
const endDateTime = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(parseDateTimeFromUnixTime(getYearMonthLastUnixTime(dateRange.value[1] as Year0BasedMonth))));
function getFinalMonthRange(): { minYearMonth: TextualYearMonth | '', maxYearMonth: TextualYearMonth | '' } | null {
if (!dateRange.value[0] || !dateRange.value[1]) {
+8 -8
View File
@@ -88,9 +88,9 @@ const {
getCurrentNumeralSystemType,
isLongDateMonthAfterYear,
isLongTime24HourFormat,
getCalendarDisplayShortYearFromUnixTime,
getCalendarDisplayShortMonthFromUnixTime,
getCalendarDisplayDayOfMonthFromUnixTime,
getCalendarDisplayShortYearFromDateTime,
getCalendarDisplayShortMonthFromDateTime,
getCalendarDisplayDayOfMonthFromDateTime,
getCalendarAlternateDate
} = useI18n();
@@ -138,21 +138,21 @@ function switchView(viewType: MenuView): void {
}
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 {
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) {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value.getFullYear(), month + 1, 1).getUnixTime(), actualNumeralSystem.value);
return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value.getFullYear(), month + 1, 1), actualNumeralSystem.value);
} 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 {
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({
+5 -5
View File
@@ -54,8 +54,8 @@ const emit = defineEmits<{
const {
isLongDateMonthAfterYear,
getCalendarDisplayShortYearFromUnixTime,
getCalendarDisplayShortMonthFromUnixTime
getCalendarDisplayShortYearFromDateTime,
getCalendarDisplayShortMonthFromDateTime
} = useI18n();
const yearRange = getAllowedYearRange();
@@ -96,14 +96,14 @@ function getYear0BasedMonthFromMonthSelectionValue(value: MonthSelectionValue):
}
function getDisplayYear(year: number): string {
return getCalendarDisplayShortYearFromUnixTime(getYearMonthDayDateTime(year, 1, 1).getUnixTime());
return getCalendarDisplayShortYearFromDateTime(getYearMonthDayDateTime(year, 1, 1));
}
function getDisplayMonth(month: number): string {
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 {
return getCalendarDisplayShortMonthFromUnixTime(getYearMonthDayDateTime(dateTime.value.year, month + 1, 1).getUnixTime());
return getCalendarDisplayShortMonthFromDateTime(getYearMonthDayDateTime(dateTime.value.year, month + 1, 1));
}
}
</script>
+4 -12
View File
@@ -38,14 +38,7 @@ import type { CalendarAlternateDate, TextualYearMonthDay, WeekDayValue } from '@
import { INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numeral.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import {
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getUnixTimeFromLocalDatetime,
getActualUnixTimeForStore,
getYearMonthDayDateTime,
parseDateTimeFromUnixTime
} from '@/lib/datetime.ts';
import { getYearMonthDayDateTime } from '@/lib/datetime.ts';
const props = defineProps<{
modelValue: TextualYearMonthDay | '';
@@ -67,7 +60,7 @@ const emit = defineEmits<{
const {
getAllLongWeekdayNames,
getAllShortWeekdayNames,
getCalendarDisplayDayOfMonthFromUnixTime,
getCalendarDisplayDayOfMonthFromDateTime,
getCalendarAlternateDates,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -105,8 +98,7 @@ const alternateDates = computed<Record<TextualYearMonthDay, string> | undefined>
});
function noTransactionInMonthDay(date: Date): boolean {
const dateTime = parseDateTimeFromUnixTime(getActualUnixTimeForStore(getUnixTimeFromLocalDatetime(date), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
return !props.dailyTotalAmounts || !props.dailyTotalAmounts[dateTime.getGregorianCalendarDay()];
return !props.dailyTotalAmounts || !props.dailyTotalAmounts[date.getDate()];
}
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 {
return getCalendarDisplayDayOfMonthFromUnixTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()).getUnixTime());
return getCalendarDisplayDayOfMonthFromDateTime(getYearMonthDayDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate()));
}
</script>
@@ -43,13 +43,6 @@ import { type CommonDateRangeSelectionProps, useDateRangeSelectionBase } from '@
import { ThemeType } from '@/core/theme.ts';
import {
getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes
} from '@/lib/datetime.ts';
interface DesktopDateRangeSelectionProps extends CommonDateRangeSelectionProps {
persistent?: boolean;
}
@@ -64,7 +57,14 @@ const emit = defineEmits<{
const theme = useTheme();
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 showState = computed<boolean>({
@@ -94,13 +94,13 @@ function cancel(): void {
watch(() => props.minTime, (newValue) => {
if (newValue) {
dateRange.value[0] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(newValue, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateRange.value[0] = getLocalDatetimeFromSameDateTimeOfUnixTime(newValue);
}
});
watch(() => props.maxTime, (newValue) => {
if (newValue) {
dateRange.value[1] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(newValue, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateRange.value[1] = getLocalDatetimeFromSameDateTimeOfUnixTime(newValue);
}
});
</script>
+12 -12
View File
@@ -94,12 +94,9 @@ import {
} from '@/core/datetime.ts';
import {
getHourIn12HourFormat,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getActualUnixTimeForStore,
getDummyUnixTimeForLocalUsage,
getSameDateTimeWithBrowserTimezone,
parseDateTimeFromUnixTimeWithTimezoneOffset,
parseDateTimeFromKnownDateTimeFormat,
getAMOrPM,
getCombinedDateAndTimeValues
@@ -108,6 +105,7 @@ import { setChildInputFocus } from '@/lib/ui/desktop.ts';
const props = defineProps<{
modelValue: number;
timezoneUtcOffset: number;
disabled?: boolean;
readonly?: boolean;
label?: string;
@@ -124,7 +122,7 @@ const {
getCurrentNumeralSystemType,
parseDateTimeFromLongDateTime,
parseDateTimeFromShortDateTime,
formatUnixTimeToLongDateTime
formatDateTimeToLongDateTime
} = useI18n();
const {
@@ -133,6 +131,8 @@ const {
isMinuteTwoDigits,
isSecondTwoDigits,
isMeridiemIndicatorFirst,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue,
generateAllHours,
generateAllMinutesOrSeconds
@@ -147,10 +147,10 @@ const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType(
const dateTime = computed<Date>({
get: () => {
return getLocalDatetimeFromUnixTime(props.modelValue);
return getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue, props.timezoneUtcOffset);
},
set: (value: Date) => {
const unixTime = getUnixTimeFromLocalDatetime(value);
const unixTime = getUnixTimeFromSameDateTimeOfLocalDatetime(value, props.timezoneUtcOffset);
if (unixTime < 0) {
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 minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isMinuteTwoDigits.value));
@@ -252,7 +252,7 @@ function onPaste(event: ClipboardEvent): void {
dt = parseDateTimeFromKnownDateTimeFormat(text, formats[0] as KnownDateTimeFormat);
if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return;
}
}
@@ -260,14 +260,14 @@ function onPaste(event: ClipboardEvent): void {
dt = parseDateTimeFromLongDateTime(text);
if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return;
}
dt = parseDateTimeFromShortDateTime(text);
if (dt) {
dateTime.value = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dt.getUnixTime(), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateTime.value = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(dt).getUnixTime());
return;
}
+11 -8
View File
@@ -43,6 +43,7 @@ import {
isNumber
} from '@/lib/common.ts';
import {
parseDateTimeFromUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime,
getDateTypeByDateRange,
@@ -95,11 +96,11 @@ const theme = useTheme();
const {
tt,
getCurrentLanguageTextDirection,
formatUnixTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth,
formatDateTimeToShortDate,
formatDateTimeToGregorianLikeShortYear,
formatDateTimeToGregorianLikeShortYearMonth,
formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear,
formatDateTimeToGregorianLikeFiscalYear,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -151,16 +152,18 @@ const allDisplayDateRanges = computed<string[]>(() => {
const allDisplayDateRanges: string[] = [];
for (const dateRange of allDateRanges.value) {
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
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) {
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime));
allDisplayDateRanges.push(formatDateTimeToGregorianLikeFiscalYear(minDateTime));
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
allDisplayDateRanges.push(formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter));
} 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') {
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 {
getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes
} from '@/lib/datetime.ts';
type DateTimePickerType = InstanceType<typeof DateTimePicker>;
const props = defineProps<CommonDateRangeSelectionProps>();
@@ -62,7 +55,14 @@ const emit = defineEmits<{
const { tt } = useI18n();
const { showToast } = useI18nUIComponents();
const { dateRange, beginDateTime, endDateTime, presetRanges, getFinalDateRange } = useDateRangeSelectionBase(props);
const {
dateRange,
beginDateTime,
endDateTime,
presetRanges,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getFinalDateRange
} = useDateRangeSelectionBase(props);
const environmentsStore = useEnvironmentsStore();
@@ -91,11 +91,11 @@ function cancel(): void {
function onSheetOpen(): void {
if (props.minTime) {
dateRange.value[0] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(props.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
dateRange.value[0] = getLocalDatetimeFromSameDateTimeOfUnixTime(props.minTime);
}
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
@@ -111,9 +111,7 @@ import { NumeralSystem } from '@/core/numeral.ts';
import { isDefined } from '@/lib/common.ts';
import {
getHourIn12HourFormat,
getLocalDatetimeFromUnixTime,
getCurrentUnixTime,
getUnixTimeFromLocalDatetime,
getAMOrPM,
getCombinedDateAndTimeValues
} from '@/lib/datetime.ts';
@@ -122,6 +120,7 @@ type DateTimePickerType = InstanceType<typeof DateTimePicker>;
const props = defineProps<{
modelValue: number;
timezoneUtcOffset: number;
initMode?: string;
show: boolean;
}>();
@@ -144,6 +143,8 @@ const {
isSecondTwoDigits,
isMeridiemIndicatorFirst,
meridiemItems,
getLocalDatetimeFromSameDateTimeOfUnixTime,
getUnixTimeFromSameDateTimeOfLocalDatetime,
getDisplayTimeValue,
generateAllHours,
generateAllMinutesOrSeconds
@@ -160,7 +161,7 @@ let resetTimePickerItemPositionItemsLastOffsetTop: number | undefined = undefine
let resetTimePickerItemPositionCheckedFrames: number | undefined = undefined;
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 timePickerItemHeight = ref<number | undefined>(undefined);
@@ -213,7 +214,7 @@ function switchMode(): void {
}
function setCurrentTime(): void {
dateTime.value = getLocalDatetimeFromUnixTime(getCurrentUnixTime());
dateTime.value = getLocalDatetimeFromSameDateTimeOfUnixTime(getCurrentUnixTime(), props.timezoneUtcOffset);
if (mode.value === 'time') {
scrollAllTimeSelectedItems();
@@ -225,7 +226,7 @@ function confirm(): void {
return;
}
const unixTime = getUnixTimeFromLocalDatetime(dateTime.value);
const unixTime = getUnixTimeFromSameDateTimeOfLocalDatetime(dateTime.value, props.timezoneUtcOffset);
if (unixTime < 0) {
showToast('Date is too early');
@@ -420,7 +421,7 @@ function onSheetOpen(): void {
mode.value = props.initMode || 'time';
if (props.modelValue) {
dateTime.value = getLocalDatetimeFromUnixTime(props.modelValue);
dateTime.value = getLocalDatetimeFromSameDateTimeOfUnixTime(props.modelValue, props.timezoneUtcOffset);
}
if (mode.value === 'time') {
+10 -8
View File
@@ -144,6 +144,7 @@ import {
isNumber
} from '@/lib/common.ts';
import {
parseDateTimeFromUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime,
getDateTypeByDateRange,
@@ -200,11 +201,11 @@ const emit = defineEmits<{
const {
tt,
formatUnixTimeToShortDate,
formatUnixTimeToGregorianLikeShortYear,
formatUnixTimeToGregorianLikeShortYearMonth,
formatDateTimeToShortDate,
formatDateTimeToGregorianLikeShortYear,
formatDateTimeToGregorianLikeShortYearMonth,
formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear,
formatDateTimeToGregorianLikeFiscalYear,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -324,18 +325,19 @@ const allDisplayDataItems = computed<TrendsBarChartData>(() => {
dateRangeKey = `${dateRange.year}-${dateRange.month}-${dateRange.day}`;
}
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
let displayDateRange = '';
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
displayDateRange = formatUnixTimeToGregorianLikeShortYear(dateRange.minUnixTime);
displayDateRange = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
displayDateRange = formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime);
displayDateRange = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
displayDateRange = formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
displayDateRange = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime);
displayDateRange = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
displayDateRange = formatUnixTimeToShortDate(dateRange.minUnixTime);
displayDateRange = formatDateTimeToShortDate(minDateTime);
}
const dataItems = allDateRangeItemsMap[dateRangeKey] || [];
+90 -27
View File
@@ -69,7 +69,31 @@ interface DateTimeFormatResult {
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 {
private static readonly tokenFormatFuncs: Record<string, DateTimeTokenFormatFunction> = {
@@ -501,28 +525,29 @@ export function getUtcOffsetByUtcOffsetMinutes(utcOffsetMinutes: number): string
}
}
export function getTimezoneOffset(timezone?: string): string {
return getUtcOffsetByUtcOffsetMinutes(getTimezoneOffsetMinutes(timezone));
export function getTimezoneOffset(unixTime: number, timezone?: string): string {
return getUtcOffsetByUtcOffsetMinutes(getTimezoneOffsetMinutes(unixTime, timezone));
}
export function getTimezoneOffsetMinutes(timezone?: string): number {
export function getTimezoneOffsetMinutes(unixTime: number, timezone?: string): number {
if (timezone) {
return moment().tz(timezone).utcOffset();
return moment.unix(unixTime).tz(timezone).utcOffset();
} else {
return moment().utcOffset();
return moment.unix(unixTime).utcOffset();
}
}
export function getBrowserTimezoneOffset(): string {
return getUtcOffsetByUtcOffsetMinutes(getBrowserTimezoneOffsetMinutes());
export function getBrowserTimezoneOffset(unixTime: number): string {
return getUtcOffsetByUtcOffsetMinutes(getBrowserTimezoneOffsetMinutes(unixTime));
}
export function getBrowserTimezoneOffsetMinutes(): number {
return -new Date().getTimezoneOffset();
export function getBrowserTimezoneOffsetMinutes(unixTime: number): number {
const date = getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(unixTime)).getUnixTime());
return -date.getTimezoneOffset();
}
export function guessTimezoneName(): string {
return moment.tz.guess(true);
export function getBrowserTimezoneName(): string {
return new Intl.DateTimeFormat().resolvedOptions().timeZone;
}
export function getLocalDatetimeFromUnixTime(unixTime: number): Date {
@@ -533,12 +558,46 @@ export function getUnixTimeFromLocalDatetime(datetime: Date): number {
return Math.floor(datetime.getTime() / 1000);
}
export function getActualUnixTimeForStore(unixTime: number, utcOffset: number, currentUtcOffset: number): number {
return unixTime - (utcOffset - currentUtcOffset) * 60;
export function getSameDateTimeWithCurrentTimezone(dateTime: DateTime): DateTime {
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 {
return unixTime + (utcOffset - currentUtcOffset) * 60;
export function getSameDateTimeWithBrowserTimezone(dateTime: DateTime): DateTime {
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 {
@@ -554,18 +613,18 @@ export function getYearMonthDayDateTime(year: number, month: number, day: number
return MomentDateTime.of(date);
}
export function parseDateTimeFromUnixTime(unixTime: number, utcOffset?: number, currentUtcOffset?: number): DateTime {
if (isNumber(utcOffset)) {
if (!isNumber(currentUtcOffset)) {
currentUtcOffset = getTimezoneOffsetMinutes();
}
unixTime = getDummyUnixTimeForLocalUsage(unixTime, utcOffset, currentUtcOffset);
}
export function parseDateTimeFromUnixTime(unixTime: number): DateTime {
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 {
const m = moment(dateTime, format.format);
@@ -586,8 +645,12 @@ export function parseDateTimeFromString(dateTime: string, format: string): DateT
return MomentDateTime.of(m);
}
export function formatUnixTime(unixTime: number, format: string, options: DateTimeFormatOptions, utcOffset?: number, currentUtcOffset?: number): string {
return parseDateTimeFromUnixTime(unixTime, utcOffset, currentUtcOffset).format(format, options);
export function formatDateTime(dateTime: DateTime, format: string, options: DateTimeFormatOptions): string {
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 {
+4 -3
View File
@@ -178,7 +178,8 @@ import {
} from './server_settings.ts';
import {
getTimezoneOffsetMinutes,
guessTimezoneName
getBrowserTimezoneName,
getCurrentUnixTime
} from './datetime.ts';
import { generateRandomUUID } from './misc.ts';
import { getBasePath } from './web.ts';
@@ -208,12 +209,12 @@ axios.interceptors.request.use((config: ApiRequestConfig) => {
config.headers.Authorization = `Bearer ${token}`;
}
config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes();
config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes(getCurrentUnixTime());
let timezoneName = getTimeZone();
if (!timezoneName || timezoneName.trim().length < 1) {
timezoneName = guessTimezoneName();
timezoneName = getBrowserTimezoneName();
}
config.headers['X-Timezone-Name'] = timezoneName;
+5 -10
View File
@@ -11,8 +11,7 @@ import {
isNumber
} from './common.ts';
import {
getBrowserTimezoneOffsetMinutes,
getDummyUnixTimeForLocalUsage
getTimezoneOffsetMinutes
} from './datetime.ts';
import {
categoryTypeToTransactionType,
@@ -33,9 +32,10 @@ export interface SetTransactionOptions {
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)) {
transaction.time = options.time;
transaction.utcOffset = getTimezoneOffsetMinutes(transaction.time, transaction.timeZone);
}
if (!options.type && options.categoryId && options.categoryId !== '0' && allCategoriesMap[options.categoryId]) {
@@ -172,14 +172,9 @@ export function setTransactionModelByTransaction(transaction: Transaction, trans
}
if (setContextData) {
transaction.utcOffset = transaction2.utcOffset;
transaction.time = transaction2.time;
transaction.timeZone = transaction2.timeZone;
if (convertContextTime) {
transaction.time = getDummyUnixTimeForLocalUsage(transaction2.time, transaction.utcOffset, getBrowserTimezoneOffsetMinutes());
} else {
transaction.time = transaction2.time;
}
transaction.utcOffset = transaction2.utcOffset;
}
transaction.sourceAccountId = transaction2.sourceAccountId;
+37 -45
View File
@@ -187,6 +187,7 @@ import {
formatCurrentTime,
formatGregorianCalendarYearDashMonthDashDay,
formatGregorianCalendarMonthDashDay,
formatDateTime,
formatUnixTime,
getBrowserTimezoneOffset,
getBrowserTimezoneOffsetMinutes,
@@ -202,7 +203,7 @@ import {
getTimeDifferenceHoursAndMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes,
guessTimezoneName,
getBrowserTimezoneName,
isDateRangeMatchFullMonths,
isDateRangeMatchFullYears,
isPM
@@ -1159,20 +1160,20 @@ export function useI18n() {
return allRecentMonthDateRanges;
}
function getAllTimezones(includeSystemDefault?: boolean): LocalizedTimezoneInfo[] {
function getAllTimezones(unixTime: number, includeSystemDefault?: boolean): LocalizedTimezoneInfo[] {
const numeralSystem = getCurrentNumeralSystemType();
const defaultTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getBrowserTimezoneOffset());
const defaultTimezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes();
const defaultTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getBrowserTimezoneOffset(unixTime));
const defaultTimezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes(unixTime);
const allTimezoneInfos: LocalizedTimezoneInfo[] = [];
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}`);
allTimezoneInfos.push({
name: timezoneInfo.timezoneName,
utcOffset: utcOffset,
utcOffsetMinutes: getTimezoneOffsetMinutes(timezoneInfo.timezoneName),
utcOffsetMinutes: getTimezoneOffsetMinutes(unixTime, timezoneInfo.timezoneName),
displayName: displayName,
displayNameWithUtcOffset: `(UTC${utcOffset}) ${displayName}`
});
@@ -1206,7 +1207,7 @@ export function useI18n() {
function getAllTimezoneTypesUsedForStatistics(currentTimezone?: string): TypeAndDisplayName[] {
const numeralSystem = getCurrentNumeralSystemType();
const currentTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(currentTimezone));
const currentTimezoneOffset = numeralSystem.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(getCurrentUnixTime(), currentTimezone));
return [
{
@@ -1789,12 +1790,11 @@ export function useI18n() {
return formatGregorianCalendarMonthDashDay(monthDay, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType, numeralSystem: numeralSystem }));
}
function formatUnixTimeToGregorianLikeYearQuarter(unixTime: number): string {
function formatDateTimeToGregorianLikeYearQuarter(dateTime: DateTime): string {
const gregorianLikeCalendarType = getGregorianLikeCalendarType();
const dateTimeFormatOptions = getDateTimeFormatOptions({ calendarType: gregorianLikeCalendarType });
const date = parseDateTimeFromUnixTime(unixTime);
const year = date.getLocalizedCalendarYear(dateTimeFormatOptions);
const quarter = date.getLocalizedCalendarQuarter(dateTimeFormatOptions);
const year = dateTime.getLocalizedCalendarYear(dateTimeFormatOptions);
const quarter = dateTime.getLocalizedCalendarQuarter(dateTimeFormatOptions);
return formatYearQuarter(year, quarter);
}
@@ -1887,9 +1887,9 @@ export function useI18n() {
return `${displayStartTime} ~ ${displayEndTime}`;
}
function getTimezoneDifferenceDisplayText(utcOffset: number): string {
function getTimezoneDifferenceDisplayText(unixTime: number, utcOffset: number): string {
const numeralSystem = getCurrentNumeralSystemType();
const defaultTimezoneOffset = getTimezoneOffsetMinutes();
const defaultTimezoneOffset = getTimezoneOffsetMinutes(unixTime);
const offsetTime = getTimeDifferenceHoursAndMinutes(utcOffset - defaultTimezoneOffset);
if (utcOffset > defaultTimezoneOffset) {
@@ -2284,19 +2284,11 @@ export function useI18n() {
}
function setTimeZone(timezone: string): void {
let timezoneOffsetMinutes = getBrowserTimezoneOffsetMinutes();
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 {
@@ -2317,7 +2309,7 @@ export function useI18n() {
logger.info(`Current timezone is ${timezone}`);
setTimeZone(timezone);
} 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('');
}
@@ -2423,36 +2415,36 @@ export function useI18n() {
isLongTimeMinuteTwoDigits,
isLongTimeSecondTwoDigits,
// 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),
getCalendarDisplayShortMonthFromUnixTime: (unixTime: number, numeralSystem?: NumeralSystem, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMM', getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem }), utcOffset, currentUtcOffset),
getCalendarDisplayDayOfMonthFromUnixTime: (unixTime: number, numeralSystem?: NumeralSystem, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDayFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem }), utcOffset, currentUtcOffset),
getCalendarDisplayShortYearFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
getCalendarDisplayShortMonthFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, 'MMM', getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
getCalendarDisplayDayOfMonthFromDateTime: (dateTime: DateTime, numeralSystem?: NumeralSystem) => formatDateTime(dateTime, getLocalizedShortDayFormat(), getDateTimeFormatOptions({ calendarType: getCurrentCalendarDisplayType().primaryCalendarType, numeralSystem: numeralSystem })),
// format date time (by date display type) functions
parseDateTimeFromLongDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat()),
parseDateTimeFromShortDateTime: (dateTime: string) => parseDateTimeFromString(dateTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat()),
formatUnixTimeToLongDateTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToShortDateTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToLongDate: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongDateFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToShortDate: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortDateFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToLongMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToShortMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortMonthDayFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToLongTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), getDateTimeFormatOptions(), utcOffset, currentUtcOffset),
formatDateTimeToLongDateTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongDateFormat() + ' ' + getLocalizedLongTimeFormat(), getDateTimeFormatOptions()),
formatDateTimeToShortDateTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortDateFormat() + ' ' + getLocalizedShortTimeFormat(), getDateTimeFormatOptions()),
formatDateTimeToLongDate: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongDateFormat(), getDateTimeFormatOptions()),
formatDateTimeToShortDate: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortDateFormat(), getDateTimeFormatOptions()),
formatDateTimeToLongMonthDay: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongMonthDayFormat(), getDateTimeFormatOptions()),
formatDateTimeToShortMonthDay: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortMonthDayFormat(), getDateTimeFormatOptions()),
formatDateTimeToLongTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongTimeFormat(), getDateTimeFormatOptions()),
formatDateTimeToShortTime: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortTimeFormat(), getDateTimeFormatOptions()),
formatGregorianTextualYearMonthDayToLongDate: (date: TextualYearMonthDay) => formatGregorianCalendarYearDashMonthDashDay(date, getLocalizedLongDateFormat(), getDateTimeFormatOptions()),
// 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),
formatUnixTimeToGregorianLikeShortYear: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset),
formatUnixTimeToGregorianLikeLongYearMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset),
formatUnixTimeToGregorianLikeShortYearMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset),
formatUnixTimeToGregorianLikeLongMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset),
formatUnixTimeToGregorianLikeShortMonth: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, 'MMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() }), utcOffset, currentUtcOffset),
formatDateTimeToGregorianLikeLongYear: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatDateTimeToGregorianLikeShortYear: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortYearFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatDateTimeToGregorianLikeLongYearMonth: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedLongYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatDateTimeToGregorianLikeShortYearMonth: (dateTime: DateTime) => formatDateTime(dateTime, getLocalizedShortYearMonthFormat(), getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatDateTimeToGregorianLikeLongMonth: (dateTime: DateTime) => formatDateTime(dateTime, 'MMMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatDateTimeToGregorianLikeShortMonth: (dateTime: DateTime) => formatDateTime(dateTime, 'MMM', getDateTimeFormatOptions({ calendarType: getGregorianLikeCalendarType() })),
formatGregorianTextualMonthDayToGregorianLikeLongMonthDay,
formatUnixTimeToGregorianLikeYearQuarter,
formatDateTimeToGregorianLikeYearQuarter,
formatYearQuarterToGregorianLikeYearQuarter,
formatUnixTimeToGregorianLikeFiscalYear,
formatDateTimeToGregorianLikeFiscalYear: (dateTime: DateTime) => formatUnixTimeToGregorianLikeFiscalYear(dateTime.getUnixTime()),
formatGregorianYearToGregorianLikeFiscalYear,
formatFiscalYearStartToGregorianLikeLongMonth,
// 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
formatDateRange,
getTimezoneDifferenceDisplayText,
+18 -19
View File
@@ -226,11 +226,11 @@ export class Transaction implements TransactionInfoResponse {
this._displayDayOfWeek = displayDayOfWeek;
}
public toCreateRequest(clientSessionId: string, actualTime?: number): TransactionCreateRequest {
public toCreateRequest(clientSessionId: string): TransactionCreateRequest {
return {
type: this.type,
categoryId: this.getCategoryId(),
time: actualTime ? actualTime : this.time,
time: this.time,
utcOffset: this.utcOffset,
sourceAccountId: this.sourceAccountId,
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();
if (this.type === TransactionType.ModifyBalance) {
@@ -255,7 +255,7 @@ export class Transaction implements TransactionInfoResponse {
return {
id: this.id,
categoryId: categoryId,
time: actualTime ? actualTime : this.time,
time: this.time,
utcOffset: this.utcOffset,
sourceAccountId: this.sourceAccountId,
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 const LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES: PartialRecord<TransactionAmountsRequestType, number> = {
'monthBeforeLast10Months': 11,
'monthBeforeLast9Months': 10,
'monthBeforeLast8Months': 9,
'monthBeforeLast7Months': 8,
'monthBeforeLast6Months': 7,
'monthBeforeLast5Months': 6,
'monthBeforeLast4Months': 5,
'monthBeforeLast3Months': 4,
'monthBeforeLast2Months': 3,
'monthBeforeLastMonth': 2,
'lastMonth': 1,
'thisMonth': 0
};
export const LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES: TransactionAmountsRequestType[] = [
'monthBeforeLast10Months',
'monthBeforeLast9Months',
'monthBeforeLast8Months',
'monthBeforeLast7Months',
'monthBeforeLast6Months',
'monthBeforeLast5Months',
'monthBeforeLast4Months',
'monthBeforeLast3Months',
'monthBeforeLast2Months',
'monthBeforeLastMonth',
'lastMonth',
'thisMonth'
];
export interface TransactionAmountsRequestParams extends PartialRecord<TransactionAmountsRequestType, StartEndTime> {
readonly useTransactionTimezone: boolean;
@@ -1009,7 +1009,6 @@ export interface TransactionOverviewResponseItem {
export interface TransactionMonthlyIncomeAndExpenseData {
readonly monthStartTime: number;
readonly monthsBeforeCurrentMonth: number;
readonly incomeAmount: number;
readonly expenseAmount: number;
readonly incompleteIncomeAmount: boolean;
+9 -17
View File
@@ -53,12 +53,7 @@ import {
splitItemsToMap,
countSplitItems
} from '@/lib/common.ts';
import {
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
parseDateTimeFromUnixTime
} from '@/lib/datetime.ts';
import { parseDateTimeFromUnixTimeWithTimezoneOffset } from '@/lib/datetime.ts';
import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts';
import { getCurrencyFraction } from '@/lib/currency.ts';
import { getFirstVisibleCategoryId } from '@/lib/category.ts';
@@ -185,14 +180,13 @@ export const useTransactionsStore = defineStore('transactions', () => {
}
if (transactionPageWrapper.items && transactionPageWrapper.items.length) {
const currentUtcOffset = getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone);
let currentMonthListIndex = -1;
let currentMonthList: TransactionMonthList | null = null;
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 transactionMonth = transactionTime.getGregorianCalendarMonth();
const transactionYearDashMonth = transactionTime.getGregorianCalendarYearDashMonth();
@@ -264,8 +258,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
}
function updateTransactionInTransactionList({ currentTransaction, defaultCurrency }: { currentTransaction: Transaction, defaultCurrency: string }): void {
const currentUtcOffset = getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone);
const transactionTime = parseDateTimeFromUnixTime(currentTransaction.time, currentTransaction.utcOffset, currentUtcOffset);
const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(currentTransaction.time, currentTransaction.utcOffset);
const transactionYear = transactionTime.getGregorianCalendarYear();
const transactionMonth = transactionTime.getGregorianCalendarMonth();
@@ -276,7 +269,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
for (const [transaction, transactionIndex] of itemAndIndex(transactionMonthList.items)) {
if (transaction.id === currentTransaction.id) {
fillTransactionObject(currentTransaction, currentUtcOffset);
fillTransactionObject(currentTransaction);
if (transactionYear !== transactionMonthList.year ||
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) {
return;
}
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
transaction.setDisplayDate(transactionTime.getGregorianCalendarYearDashMonthDashDay(), transactionTime.getGregorianCalendarDay(), transactionTime.getWeekDay());
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> {
return new Promise((resolve, reject) => {
const actualTime = getActualUnixTimeForStore(transaction.time, transaction.utcOffset, getBrowserTimezoneOffsetMinutes());
let promise: ApiResponsePromise<TransactionInfoResponse>;
if (transaction.type !== TransactionType.Expense &&
@@ -1041,9 +1033,9 @@ export const useTransactionsStore = defineStore('transactions', () => {
}
if (!isEdit) {
promise = services.addTransaction(transaction.toCreateRequest(clientSessionId, actualTime));
promise = services.addTransaction(transaction.toCreateRequest(clientSessionId));
} else {
promise = services.modifyTransaction(transaction.toModifyRequest(actualTime));
promise = services.modifyTransaction(transaction.toModifyRequest());
}
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 { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { getMapProvider } from '@/lib/server_settings.ts';
import { getMapWebsite } from '@/lib/map/index.ts';
import { getLicense, getThirdPartyLicenses } from '@/lib/licenses.ts';
@@ -16,7 +17,7 @@ import { formatDisplayVersion, getClientDisplayVersion, getClientBuildTime } fro
import { clearBrowserCaches } from '@/lib/ui/common.ts';
export function useAboutPageBase() {
const { tt, formatUnixTimeToLongDateTime } = useI18n();
const { tt, formatDateTimeToLongDateTime } = useI18n();
const systemsStore = useSystemsStore();
const exchangeRatesStore = useExchangeRatesStore();
@@ -41,7 +42,8 @@ export function useAboutPageBase() {
return time;
}
return formatUnixTimeToLongDateTime(parseInt(time));
const buildDateTime = parseDateTimeFromUnixTime(parseInt(time));
return formatDateTimeToLongDateTime(buildDateTime);
});
const exchangeRatesData = computed<LatestExchangeRateResponse | undefined>(() => exchangeRatesStore.latestExchangeRates.data);
+8 -3
View File
@@ -12,9 +12,10 @@ import type {
} from '@/models/exchange_rate.ts';
import { getExchangedAmountByRate } from '@/lib/numeral.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
export function useExchangeRatesPageBase() {
const { getAllDisplayExchangeRates, formatUnixTimeToLongDate, parseAmountFromWesternArabicNumerals } = useI18n();
const { getAllDisplayExchangeRates, formatDateTimeToLongDate, parseAmountFromWesternArabicNumerals } = useI18n();
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
@@ -27,8 +28,12 @@ export function useExchangeRatesPageBase() {
const isUserCustomExchangeRates = computed<boolean>(() => exchangeRatesStore.isUserCustomExchangeRates);
const exchangeRatesDataUpdateTime = computed<string>(() => {
const exchangeRatesLastUpdateTime = exchangeRatesStore.exchangeRatesLastUpdateTime;
return exchangeRatesLastUpdateTime ? formatUnixTimeToLongDate(exchangeRatesLastUpdateTime) : '';
if (!exchangeRatesStore.exchangeRatesLastUpdateTime) {
return '';
}
const exchangeRatesLastUpdateTime = parseDateTimeFromUnixTime(exchangeRatesStore.exchangeRatesLastUpdateTime);
return formatDateTimeToLongDate(exchangeRatesLastUpdateTime);
});
const availableExchangeRates = computed<LocalizedLatestExchangeRate[]>(() => {
+13 -11
View File
@@ -17,12 +17,14 @@ import type {
TransactionOverviewResponseItem
} from '@/models/transaction.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
export function useHomePageBase() {
const {
formatUnixTimeToLongDate,
formatUnixTimeToLongMonthDay,
formatUnixTimeToGregorianLikeLongYear,
formatUnixTimeToGregorianLikeLongMonth,
formatDateTimeToLongDate,
formatDateTimeToLongMonthDay,
formatDateTimeToGregorianLikeLongYear,
formatDateTimeToGregorianLikeLongMonth,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -57,19 +59,19 @@ export function useHomePageBase() {
const displayDateRange = computed<TransactionOverviewDisplayTime>(() => {
return {
today: {
displayTime: formatUnixTimeToLongDate(overviewStore.transactionDataRange.today.startTime),
displayTime: formatDateTimeToLongDate(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.today.startTime)),
},
thisWeek: {
startTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisWeek.startTime),
endTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisWeek.endTime)
startTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisWeek.startTime)),
endTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisWeek.endTime))
},
thisMonth: {
displayTime: formatUnixTimeToGregorianLikeLongMonth(overviewStore.transactionDataRange.thisMonth.startTime),
startTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisMonth.startTime),
endTime: formatUnixTimeToLongMonthDay(overviewStore.transactionDataRange.thisMonth.endTime)
displayTime: formatDateTimeToGregorianLikeLongMonth(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.startTime)),
startTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.startTime)),
endTime: formatDateTimeToLongMonthDay(parseDateTimeFromUnixTime(overviewStore.transactionDataRange.thisMonth.endTime))
},
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 { 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 {
readonly day: number;
@@ -25,7 +31,7 @@ export function useAccountEditPageBase() {
const clientSessionId = ref<string>('');
const loading = 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 title = computed<string>(() => {
@@ -89,6 +95,18 @@ export function useAccountEditPageBase() {
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 {
for (const item of allAvailableMonthDays.value) {
if (item.day === statementDate) {
@@ -99,6 +117,23 @@ export function useAccountEditPageBase() {
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 {
if (!isSubAccount && !account.category) {
return 'Account category cannot be blank';
@@ -122,7 +157,7 @@ export function useAccountEditPageBase() {
return false;
}
const subAccount = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime());
const subAccount = account.value.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
subAccounts.value.push(subAccount);
return true;
}
@@ -133,7 +168,7 @@ export function useAccountEditPageBase() {
if (newAccount.subAccounts && newAccount.subAccounts.length > 0) {
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);
subAccounts.value.push(subAccount);
@@ -163,7 +198,10 @@ export function useAccountEditPageBase() {
allAvailableMonthDays,
isAccountSupportCreditCardStatementDate,
// functions
getCurrentUnixTimeForNewAccount,
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate,
updateAccountBalanceTime,
isNewAccount,
addSubAccount,
setAccount
@@ -2,7 +2,6 @@ import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
@@ -25,7 +24,8 @@ import { replaceAll } from '@/lib/common.ts';
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime
parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
export function useReconciliationStatementPageBase() {
@@ -33,15 +33,14 @@ export function useReconciliationStatementPageBase() {
tt,
getAllAccountBalanceTrendChartTypes,
getAllStatisticsDateAggregationTypesWithShortName,
formatUnixTimeToLongDateTime,
formatUnixTimeToLongDate,
formatUnixTimeToShortTime,
formatUnixTimeToGregorianDefaultDateTime,
formatDateTimeToLongDateTime,
formatDateTimeToLongDate,
formatDateTimeToShortTime,
formatDateTimeToGregorianDefaultDateTime,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
const settingsStore = useSettingsStore();
const userStore = useUserStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
@@ -53,7 +52,6 @@ export function useReconciliationStatementPageBase() {
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllAccountBalanceTrendChartTypes());
@@ -79,11 +77,13 @@ export function useReconciliationStatementPageBase() {
const allCategoriesMap = computed<Record<string, TransactionCategory>>(() => transactionCategoriesStore.allTransactionCategoriesMap);
const displayStartDateTime = computed<string>(() => {
return formatUnixTimeToLongDateTime(startTime.value);
const dateTime = parseDateTimeFromUnixTime(startTime.value);
return formatDateTimeToLongDateTime(dateTime);
});
const displayEndDateTime = computed<string>(() => {
return formatUnixTimeToLongDateTime(endTime.value);
const dateTime = parseDateTimeFromUnixTime(endTime.value);
return formatDateTimeToLongDateTime(dateTime);
});
const displayTotalInflows = computed<string>(() => {
@@ -160,15 +160,22 @@ export function useReconciliationStatementPageBase() {
}
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 {
return formatUnixTimeToLongDate(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value);
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDate(dateTime);
}
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 {
@@ -227,7 +234,7 @@ export function useReconciliationStatementPageBase() {
const transactions = reconciliationStatements.value?.transactions ?? [];
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);
let categoryName = transaction.categoryName;
let displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.sourceAmount);
@@ -260,7 +267,7 @@ export function useReconciliationStatementPageBase() {
}
return [
formatUnixTimeToGregorianDefaultDateTime(transactionTime),
formatDateTimeToGregorianDefaultDateTime(transactionTime),
type,
categoryName,
displayAmount,
@@ -282,7 +289,6 @@ export function useReconciliationStatementPageBase() {
// computed states
firstDayOfWeek,
fiscalYearStart,
currentTimezoneOffsetMinutes,
defaultCurrency,
allChartTypes,
allDateAggregationTypes,
@@ -303,6 +309,7 @@ export function useReconciliationStatementPageBase() {
getDisplayDateTime,
getDisplayDate,
getDisplayTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone,
getDisplaySourceAmount,
getDisplayDestinationAmount,
@@ -15,6 +15,7 @@ import { CategoryType } from '@/core/category.ts';
import type { Account } from '@/models/account.ts';
import { isObjectEmpty } from '@/lib/common.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts';
export function useAppSettingPageBase() {
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 allCurrencySortingTypes = computed<TypeAndDisplayName[]>(() => getAllCurrencySortingTypes());
@@ -28,7 +28,11 @@ import type {
} from '@/models/transaction.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';
export function useStatisticsTransactionPageBase() {
@@ -37,8 +41,8 @@ export function useStatisticsTransactionPageBase() {
getAllDateRanges,
getAllStatisticsSortingTypes,
getAllStatisticsDateAggregationTypes,
formatUnixTimeToLongDateTime,
formatUnixTimeToGregorianLikeLongYearMonth,
formatDateTimeToLongDateTime,
formatDateTimeToGregorianLikeLongYearMonth,
formatDateRange,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -88,11 +92,11 @@ export function useStatisticsTransactionPageBase() {
const queryStartTime = computed<string>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return formatUnixTimeToLongDateTime(query.value.categoricalChartStartTime);
return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.categoricalChartStartTime));
} 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) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartStartTime);
return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.assetTrendsChartStartTime));
} else {
return '';
}
@@ -100,11 +104,11 @@ export function useStatisticsTransactionPageBase() {
const queryEndTime = computed<string>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return formatUnixTimeToLongDateTime(query.value.categoricalChartEndTime);
return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.categoricalChartEndTime));
} 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) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartEndTime);
return formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.assetTrendsChartEndTime));
} else {
return '';
}
@@ -36,6 +36,8 @@ import {
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes,
getSameDateTimeWithCurrentTimezone,
parseDateTimeFromUnixTimeWithBrowserTimezone,
getCurrentUnixTime
} from '@/lib/datetime.ts';
@@ -92,14 +94,14 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
const transaction = ref<Transaction | TransactionTemplate>(createNewTransactionModel(transactionDefaultType));
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 defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const defaultAccountId = computed<string>(() => userStore.currentUserDefaultAccountId);
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
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 allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allAccountsMap = computed<Record<string, Account>>(() => accountsStore.allAccountsMap);
@@ -274,7 +276,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
});
const transactionTimezoneTimeDifference = computed<string>(() => {
return getTimezoneDifferenceDisplayText(transaction.value.utcOffset);
return getTimezoneDifferenceDisplayText(transaction.value.time, transaction.value.utcOffset);
});
const geoLocationStatusInfo = computed<string>(() => {
@@ -331,8 +333,12 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
return !!inputEmptyProblemMessage.value;
});
function getCurrentUnixTimeForNewTransaction(): number {
return getSameDateTimeWithCurrentTimezone(parseDateTimeFromUnixTimeWithBrowserTimezone(getCurrentUnixTime())).getUnixTime();
}
function createNewTransactionModel(transactionType?: number): Transaction | TransactionTemplate {
const now: number = getCurrentUnixTime();
const now: number = getCurrentUnixTimeForNewTransaction();
const currentTimezone: string = settingsStore.appSettings.timeZone;
let defaultType: TransactionType = TransactionType.Expense;
@@ -343,7 +349,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
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) {
newTransaction = TransactionTemplate.createNewTransactionTemplate(newTransaction);
@@ -352,6 +358,25 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
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 {
if (swapAccount) {
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 {
// constants
isSupportGeoLocation,
@@ -460,6 +476,8 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
inputIsEmpty,
// functions
createNewTransactionModel,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData,
getDisplayAmount,
getTransactionPictureUrl
@@ -23,13 +23,12 @@ import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts'
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime,
getDummyUnixTimeForLocalUsage,
getSameDateTimeWithBrowserTimezone,
getCurrentDateTime,
parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset,
getYearMonthFirstUnixTime,
isDateRangeMatchOneMonth
} from '@/lib/datetime.ts';
@@ -76,10 +75,10 @@ export function useTransactionListPageBase() {
tt,
getAllDateRanges,
getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime,
formatUnixTimeToLongDate,
formatUnixTimeToShortTime,
formatUnixTimeToGregorianLikeLongYearMonth,
formatDateTimeToLongDateTime,
formatDateTimeToLongDate,
formatDateTimeToShortTime,
formatDateTimeToGregorianLikeLongYearMonth,
formatDateRange,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -98,7 +97,6 @@ export function useTransactionListPageBase() {
const currentCalendarDate = ref<TextualYearMonthDay | ''>('');
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
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);
});
const queryMinTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.minTime));
const queryMaxTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.maxTime));
const queryMinTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.minTime)));
const queryMaxTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.maxTime)));
const queryMonthlyData = computed<boolean>(() => isDateRangeMatchOneMonth(query.value.minTime, query.value.maxTime));
const queryMonth = computed<Year0BasedMonth>(() => {
if (!query.value.minTime || !query.value.maxTime) {
@@ -235,8 +233,8 @@ export function useTransactionListPageBase() {
return displayAmount.join(' ~ ');
});
const transactionCalendarMinDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(query.value.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
const transactionCalendarMaxDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(query.value.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
const transactionCalendarMinDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(query.value.minTime)).getUnixTime()));
const transactionCalendarMaxDate = computed<Date>(() => getLocalDatetimeFromUnixTime(getSameDateTimeWithBrowserTimezone(parseDateTimeFromUnixTime(query.value.maxTime)).getUnixTime()));
const currentMonthTransactionData = computed<TransactionMonthList | null>(() => {
const allTransactions = transactionsStore.transactions;
@@ -284,6 +282,10 @@ export function useTransactionListPageBase() {
return false;
}
function isSameAsDefaultTimezoneOffsetMinutes(transaction: Transaction): boolean {
return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time);
}
function formatAmount(amount: number, hideAmount: boolean, currencyCode: string): string {
if (hideAmount) {
return formatAmountToLocalizedNumeralsWithCurrency(DISPLAY_HIDDEN_AMOUNT, currencyCode);
@@ -293,15 +295,18 @@ export function useTransactionListPageBase() {
}
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 {
return formatUnixTimeToLongDate(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value);
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
return formatDateTimeToLongDate(dateTime);
}
function getDisplayLongYearMonth(transactionMonthList: TransactionMonthList): string {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(transactionMonthList.yearDashMonth));
const yearMonthDateTime = parseDateTimeFromUnixTime(getYearMonthFirstUnixTime(transactionMonthList.yearDashMonth));
return formatDateTimeToGregorianLikeLongYearMonth(yearMonthDateTime);
}
function getDisplayTimezone(transaction: Transaction): string {
@@ -310,8 +315,10 @@ export function useTransactionListPageBase() {
}
function getDisplayTimeInDefaultTimezone(transaction: Transaction): string {
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(settingsStore.appSettings.timeZone));
return `${formatUnixTimeToLongDateTime(transaction.time)} (UTC${utcOffset})`;
const timezoneOffsetMinutes = getTimezoneOffsetMinutes(transaction.time);
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, timezoneOffsetMinutes);
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getUtcOffsetByUtcOffsetMinutes(timezoneOffsetMinutes));
return `${formatDateTimeToLongDateTime(dateTime)} (UTC${utcOffset})`;
}
function getDisplayAmount(transaction: Transaction): string {
@@ -372,7 +379,6 @@ export function useTransactionListPageBase() {
customMaxDatetime,
currentCalendarDate,
// computed states
currentTimezoneOffsetMinutes,
firstDayOfWeek,
fiscalYearStart,
defaultCurrency,
@@ -410,6 +416,7 @@ export function useTransactionListPageBase() {
canAddTransaction,
// functions
hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTime,
getDisplayLongDate,
getDisplayLongYearMonth,
+1 -5
View File
@@ -202,12 +202,10 @@ import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useOverviewStore } from '@/stores/overview.ts';
import { entries } from '@/core/base.ts';
import { type NumeralSystem } from '@/core/numeral.ts';
import { DateRange } from '@/core/datetime.ts';
import { ThemeType } from '@/core/theme.ts';
import {
type TransactionAmountsRequestType,
type TransactionMonthlyIncomeAndExpenseData,
LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES
} from '@/models/transaction.ts';
@@ -280,8 +278,7 @@ const monthlyIncomeAndExpenseData = computed<TransactionMonthlyIncomeAndExpenseD
return data;
}
for (const [type, monthDiff] of entries(LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES)) {
const amountRequestType = type as TransactionAmountsRequestType;
for (const amountRequestType of LATEST_12MONTHS_TRANSACTION_AMOUNTS_REQUEST_TYPES) {
const dateRange = overviewStore.transactionDataRange[amountRequestType];
if (!dateRange) {
@@ -292,7 +289,6 @@ const monthlyIncomeAndExpenseData = computed<TransactionMonthlyIncomeAndExpenseD
data.push({
monthStartTime: dateRange.startTime,
monthsBeforeCurrentMonth: monthDiff,
incomeAmount: item?.incomeAmount || 0,
expenseAmount: item?.expenseAmount || 0,
incompleteIncomeAmount: item ? item.incompleteIncomeAmount : true,
@@ -145,7 +145,9 @@
<date-time-select
:disabled="loading || submitting"
: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" />
</v-col>
<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 { isNumber } from '@/lib/common.ts';
import { getCurrentUnixTime } from '@/lib/datetime.ts';
import { generateRandomUUID } from '@/lib/misc.ts';
import {
@@ -242,6 +243,9 @@ const {
allAccountTypes,
allAvailableMonthDays,
isAccountSupportCreditCardStatementDate,
getCurrentUnixTimeForNewAccount,
getDefaultTimezoneOffsetMinutes,
updateAccountBalanceTime,
isNewAccount,
addSubAccount,
setAccount
@@ -275,7 +279,7 @@ const accountAmountTitle = computed<string>(() => {
const isAccountModified = computed<boolean>(() => {
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 {
return true;
}
@@ -289,7 +293,7 @@ function open(options?: { id?: string, currentAccount?: Account, category?: numb
loading.value = true;
submitting.value = false;
const newAccount = Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime());
const newAccount = Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
account.value.fillFrom(newAccount);
subAccounts.value = [];
currentAccountIndex.value = -1;
@@ -149,7 +149,7 @@
<template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span>
<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 #item.type="{ item }">
<v-chip label variant="outlined" size="x-small"
@@ -323,7 +323,6 @@ const {
startTime,
endTime,
reconciliationStatements,
currentTimezoneOffsetMinutes,
fiscalYearStart,
allChartTypes,
allDateAggregationTypes,
@@ -341,6 +340,7 @@ const {
setReconciliationStatements,
getDisplayTransactionType,
getDisplayDateTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone,
getDisplaySourceAmount,
getDisplayDestinationAmount,
+5 -4
View File
@@ -151,9 +151,10 @@ import {
} from '@/models/transaction.ts';
import {
parseDateTimeFromUnixTime,
getShiftedDateRangeAndDateType,
getDateTypeByDateRange,
getDateRangeByDateType,
getDateRangeByDateType
} from '@/lib/datetime.ts';
import {
@@ -188,7 +189,7 @@ const {
tt,
getAllDateRanges,
getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime,
formatDateTimeToLongDateTime,
formatDateRange
} = useI18n();
@@ -221,8 +222,8 @@ const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explor
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplore, true));
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 displayQueryStartTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.startTime));
const displayQueryEndTime = computed<string>(() => formatUnixTimeToLongDateTime(query.value.endTime));
const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.startTime)));
const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.endTime)));
const allTabs = computed<{ name: string, value: ExplorePageTabType }[]>(() => {
return [
@@ -14,7 +14,7 @@
<template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span>
<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 #item.type="{ item }">
<v-chip label variant="outlined" size="x-small"
@@ -76,7 +76,6 @@ import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { useUserStore } from '@/stores/user.ts';
import { useExploresStore } from '@/stores/explore.ts';
@@ -89,7 +88,7 @@ import {
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
import {
@@ -109,19 +108,17 @@ const emit = defineEmits<{
const {
tt,
formatUnixTimeToLongDateTime,
formatUnixTimeToGregorianDefaultDateTime,
formatDateTimeToLongDateTime,
formatDateTimeToGregorianDefaultDateTime,
formatAmountToWesternArabicNumeralsWithoutDigitGrouping,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
const settingsStore = useSettingsStore();
const userStore = useUserStore();
const exploresStore = useExploresStore();
const currentPage = ref<number>(1);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => exploresStore.filteredTransactions);
@@ -163,7 +160,12 @@ const dataTableHeaders = computed<object[]>(() => {
});
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 {
@@ -234,7 +236,7 @@ function buildExportResults(): { headers: string[], data: string[][] } | undefin
],
data: filteredTransactions.value
.map(transaction => {
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value).getUnixTime();
const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset);
const type = getDisplayTransactionType(transaction);
let categoryName = transaction.secondaryCategoryName;
@@ -254,7 +256,7 @@ function buildExportResults(): { headers: string[], data: string[][] } | undefin
const description = transaction.comment || '';
return [
formatUnixTimeToGregorianDefaultDateTime(transactionTime),
formatDateTimeToGregorianDefaultDateTime(transactionTime),
type,
categoryName,
displayAmount,
@@ -41,7 +41,7 @@ import { DISPLAY_HIDDEN_AMOUNT, INCOMPLETE_AMOUNT_SUFFIX } from '@/consts/numera
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';
export interface MonthlyIncomeAndExpenseCardClickEvent {
@@ -64,7 +64,7 @@ const emit = defineEmits<{
const {
tt,
getCurrentLanguageTextDirection,
formatUnixTimeToGregorianLikeShortMonth,
formatDateTimeToGregorianLikeShortMonth,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -98,11 +98,9 @@ const chartOptions = computed<object>(() => {
const expenseIncomeAmountColor = getExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor, props.isDarkMode);
if (props.data) {
const currentMonthFirstUnixTime = getThisMonthFirstUnixTime();
for (const item of props.data) {
const monthFirstUnixTime = getUnixTimeBeforeUnixTime(currentMonthFirstUnixTime, item.monthsBeforeCurrentMonth, 'months');
const monthShortName = formatUnixTimeToGregorianLikeShortMonth(monthFirstUnixTime);
const monthStartDateTime = parseDateTimeFromUnixTime(item.monthStartTime);
const monthShortName = formatDateTimeToGregorianLikeShortMonth(monthStartDateTime);
monthNames.push(monthShortName);
incomeAmounts.push(item.incomeAmount);
+4 -6
View File
@@ -534,8 +534,8 @@
<td class="transaction-table-column-time">
<div class="d-flex flex-column">
<span>{{ getDisplayTime(transaction) }}</span>
<span class="text-caption" v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimezone(transaction) }}</span>
<v-tooltip activator="parent" v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ getDisplayTimeInDefaultTimezone(transaction) }}</v-tooltip>
<span class="text-caption" v-if="!isSameAsDefaultTimezoneOffsetMinutes(transaction)">{{ getDisplayTimezone(transaction) }}</span>
<v-tooltip activator="parent" v-if="!isSameAsDefaultTimezoneOffsetMinutes(transaction)">{{ getDisplayTimeInDefaultTimezone(transaction) }}</v-tooltip>
</div>
</td>
<td class="transaction-table-column-category">
@@ -691,8 +691,6 @@ import {
import {
getCurrentUnixTime,
parseDateTimeFromUnixTime,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
getDayFirstUnixTimeBySpecifiedUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime,
@@ -775,7 +773,6 @@ const {
customMinDatetime,
customMaxDatetime,
currentCalendarDate,
currentTimezoneOffsetMinutes,
firstDayOfWeek,
fiscalYearStart,
defaultCurrency,
@@ -809,6 +806,7 @@ const {
transactionCalendarMaxDate,
currentMonthTransactionData,
hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
canAddTransaction,
getDisplayTime,
getDisplayLongDate,
@@ -1234,7 +1232,7 @@ function changePageType(type: number): 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 (!query.value.minTime || !query.value.maxTime) {
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes());
customMaxDatetime.value = getCurrentUnixTime();
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
} else {
customMaxDatetime.value = query.value.maxTime;
@@ -81,7 +81,7 @@
<template #item.time="{ item }">
<span>{{ getDisplayDateTime(item) }}</span>
<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 #item.type="{ value }">
<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';
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes
getTimezoneOffsetMinutes,
parseDateTimeFromUnixTime,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
import { formatCoordinate } from '@/lib/coordinate.ts';
import {
@@ -495,7 +497,7 @@ const props = defineProps<{
const {
tt,
getCurrentNumeralSystemType,
formatUnixTimeToLongDateTime,
formatDateTimeToLongDateTime,
formatAmountToLocalizedNumeralsWithCurrency,
getCategorizedAccountsWithDisplayBalance
} = useI18n();
@@ -536,7 +538,6 @@ const currentDescriptionFilterValue = ref<string | null>(null);
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType);
@@ -1129,8 +1130,8 @@ const displayFilterCustomDateRange = computed<string>(() => {
return '';
}
const minDisplayTime = formatUnixTimeToLongDateTime(filters.value.minDatetime);
const maxDisplayTime = formatUnixTimeToLongDateTime(filters.value.maxDatetime);
const minDisplayTime = formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(filters.value.minDatetime));
const maxDisplayTime = formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(filters.value.maxDatetime));
return `${minDisplayTime} - ${maxDisplayTime}`
});
@@ -1272,7 +1273,12 @@ function isTagValid(tagIds: string[], tagIndex: number): boolean {
}
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 {
@@ -54,7 +54,10 @@ import { KnownFileType } from '@/core/file.ts';
import type { ImportTransactionRequest, ImportTransactionRequestItem } from '@/models/imported_transaction.ts';
import { isDefined } from '@/lib/common.ts';
import { getBrowserTimezoneOffsetMinutes } from '@/lib/datetime.ts';
import {
getTimezoneOffsetMinutes,
getCurrentUnixTime
} from '@/lib/datetime.ts';
import {
openTextFileContent,
startDownloadFile
@@ -121,7 +124,7 @@ function parse(row, index) {
return {
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')}
categoryName: row[4], // ${tt('sample.importTransactionCustomScript.fieldCategoryNameDescription')}
sourceAccountName: row[5], // ${tt('sample.importTransactionCustomScript.fieldSourceAccountNameDescription')}
@@ -250,7 +250,9 @@
:readonly="mode === TransactionEditPageMode.View"
:disabled="loading || submitting || (mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance)"
:label="tt('Transaction Time')"
v-model="transaction.time"
:timezone-utc-offset="transaction.utcOffset"
:model-value="transaction.time"
@update:model-value="updateTransactionTime"
@error="onShowDateTimeError" />
</v-col>
<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')"
:items="allTimezones"
:no-data-text="tt('No results')"
v-model="transaction.timeZone"
:model-value="transaction.timeZone"
@update:model-value="updateTransactionTimezone"
>
<template #selection="{ item }">
<span class="text-truncate" v-if="transaction.timeZone || transaction.timeZone === ''">
@@ -642,6 +645,8 @@ const {
inputEmptyProblemMessage,
inputIsEmpty,
createNewTransactionModel,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData,
getTransactionPictureUrl
} = 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(
transaction.value,
newTransaction,
@@ -735,8 +740,7 @@ function setTransaction(newTransaction: Transaction | null, options: SetTransact
tagIds: options.tagIds,
comment: options.comment
},
setContextData,
convertContextTime
setContextData
);
}
@@ -758,7 +762,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
initTagIds.value = options.tagIds;
const newTransaction = createNewTransactionModel(options.type);
setTransaction(newTransaction, options, true, false);
setTransaction(newTransaction, options, true);
const promises: Promise<unknown>[] = [
accountsStore.loadAllAccounts({ force: false }),
@@ -769,7 +773,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
if (props.type === TransactionEditPageType.Transaction) {
if (options && options.id) {
if (options.currentTransaction) {
setTransaction(options.currentTransaction, options, true, true);
setTransaction(options.currentTransaction, options, true);
}
mode.value = TransactionEditPageMode.View;
@@ -781,10 +785,10 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
editId.value = null;
if (options.template) {
setTransaction(options.template, options, false, false);
setTransaction(options.template, options, false);
addByTemplateId.value = options.template.id;
} 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
@@ -809,7 +813,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
if (options && options.id) {
if (options.currentTemplate) {
setTransaction(options.currentTemplate, options, false, false);
setTransaction(options.currentTemplate, options, false);
(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) {
const transaction: Transaction = responses[3];
setTransaction(transaction, options, true, true);
setTransaction(transaction, options, true);
originalTransactionEditable.value = transaction.editable;
} else if (props.type === TransactionEditPageType.Template && options && options.id && responses[3] && responses[3] instanceof TransactionTemplate) {
const template: TransactionTemplate = responses[3];
setTransaction(template, options, false, false);
setTransaction(template, options, false);
if (!(transaction.value instanceof TransactionTemplate)) {
transaction.value = TransactionTemplate.createNewTransactionTemplate(transaction.value);
@@ -862,7 +866,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
(transaction.value as TransactionTemplate).fillFrom(template);
} else {
setTransaction(null, options, true, true);
setTransaction(null, options, true);
}
loading.value = false;
@@ -1003,7 +1007,7 @@ function duplicate(withTime?: boolean, withGeoLocation?: boolean): void {
if (!withTime) {
transaction.value.time = getCurrentUnixTime();
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) {
@@ -218,6 +218,7 @@ import { type UserExternalAuthInfoResponse } from '@/models/user_external_auth.t
import { type TokenInfoResponse, SessionDeviceType, SessionInfo } from '@/models/token.ts';
import { isEquals } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { parseSessionInfo } from '@/lib/session.ts';
import {
isAPITokenEnabled,
@@ -253,7 +254,7 @@ class DesktopPageLinkedThirdPartyLogin {
this.externalAuthType = externalAuthInfoResponse.externalAuthType;
this.linked = externalAuthInfoResponse.linked;
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') {
this.displayName = getLocalizedOAuth2ProviderName(externalAuthInfoResponse.externalAuthType, getOIDCCustomDisplayNames());
@@ -277,7 +278,7 @@ class DesktopPageSessionInfo extends SessionInfo {
public constructor(sessionInfo: SessionInfo) {
super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.deviceName, sessionInfo.lastSeen);
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 {
@@ -306,7 +307,7 @@ type SnackBarType = InstanceType<typeof SnackBar>;
const {
tt,
formatUnixTimeToLongDateTime,
formatDateTimeToLongDateTime,
getLocalizedOAuth2ProviderName,
setLanguage
} = useI18n();
+8 -3
View File
@@ -129,6 +129,7 @@ import { useUserStore } from '@/stores/user.ts';
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
import { findNameByValue } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { getClientDisplayVersion, getDesktopVersionPath } from '@/lib/version.ts';
import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.ts';
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
@@ -137,7 +138,7 @@ const props = defineProps<{
f7router: Router.Router;
}>();
const { tt, formatUnixTimeToLongDate, initLocale } = useI18n();
const { tt, formatDateTimeToLongDate, initLocale } = useI18n();
const { showToast, showConfirm } = useI18nUIComponents();
const { allThemes, allTimezones, timeZone, isAutoUpdateExchangeRatesData, showAccountBalance } = useAppSettingPageBase();
@@ -197,8 +198,12 @@ const isEnableAnimate = computed<boolean>({
const isEnableApplicationLock = computed<boolean>(() => settingsStore.appSettings.applicationLock);
const exchangeRatesLastUpdateDate = computed<string>(() => {
const exchangeRatesLastUpdateTime = exchangeRatesStore.exchangeRatesLastUpdateTime;
return exchangeRatesLastUpdateTime ? formatUnixTimeToLongDate(exchangeRatesLastUpdateTime) : '';
if (!exchangeRatesStore.exchangeRatesLastUpdateTime) {
return '';
}
const exchangeRatesLastUpdateTime = parseDateTimeFromUnixTime(exchangeRatesStore.exchangeRatesLastUpdateTime);
return formatDateTimeToLongDate(exchangeRatesLastUpdateTime);
});
function switchToDesktopVersion(): void {
+15 -8
View File
@@ -233,8 +233,10 @@
</div>
</template>
<date-time-selection-sheet :init-mode="accountContext.balanceDateTimeSheetMode"
:timezone-utc-offset="getDefaultTimezoneOffsetMinutes(account)"
:model-value="account.balanceTime"
v-model:show="accountContext.showBalanceDateTimeSheet"
v-model="account.balanceTime">
@update:model-value="updateAccountBalanceTime(account, $event)">
</date-time-selection-sheet>
</f7-list-item>
@@ -475,8 +477,10 @@
</div>
</template>
<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="subAccount.balanceTime">
@update:model-value="updateAccountBalanceTime(subAccount, $event)">
</date-time-selection-sheet>
</f7-list-item>
@@ -538,8 +542,7 @@ import { isDefined, findDisplayNameByType } from '@/lib/common.ts';
import { generateRandomUUID } from '@/lib/misc.ts';
import {
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
interface AccountContext {
@@ -561,8 +564,8 @@ const {
tt,
getAllCurrencies,
getCurrencyName,
formatUnixTimeToLongDate,
formatUnixTimeToLongTime,
formatDateTimeToLongDate,
formatDateTimeToLongTime,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
@@ -582,7 +585,9 @@ const {
allAccountTypes,
allAvailableMonthDays,
isAccountSupportCreditCardStatementDate,
getDefaultTimezoneOffsetMinutes,
getAccountCreditCardStatementDate,
updateAccountBalanceTime,
isNewAccount,
addSubAccount,
setAccount
@@ -621,7 +626,8 @@ function formatAccountBalanceDate(account: Account): string {
return '';
}
return formatUnixTimeToLongDate(getActualUnixTimeForStore(account.balanceTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
return formatDateTimeToLongDate(dateTime);
}
function formatAccountBalanceTime(account: Account): string {
@@ -629,7 +635,8 @@ function formatAccountBalanceTime(account: Account): string {
return '';
}
return formatUnixTimeToLongTime(getActualUnixTimeForStore(account.balanceTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(account.balanceTime, getTimezoneOffsetMinutes(account.balanceTime));
return formatDateTimeToLongTime(dateTime);
}
function init(): void {
@@ -51,10 +51,10 @@
</template>
<template #footer>
<div v-if="dateRange.isUserCustomRange && queryDateRangeType === dateRange.type && startTime && endTime">
<span>{{ displayStartTime }}</span>
<span>{{ displayStartDateTime }}</span>
<span>&nbsp;-&nbsp;</span>
<br/>
<span>{{ displayEndTime }}</span>
<span>{{ displayEndDateTime }}</span>
</div>
</template>
</f7-list-item>
@@ -227,7 +227,7 @@
<div class="transaction-footer display-flex justify-content-space-between">
<div class="flex-shrink-0">
<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 class="account-balance flex-shrink-1">
<span>{{ isCurrentLiabilityAccount ? tt('Outstanding Balance') : tt('Balance') }}</span>
@@ -388,7 +388,6 @@ const {
tt,
getCurrentLanguageTextDirection,
getAllDateRanges,
formatUnixTimeToLongDateTime,
formatNumberToLocalizedNumerals
} = useI18n();
@@ -402,7 +401,6 @@ const {
firstDayOfWeek,
fiscalYearStart,
allDateAggregationTypes,
currentTimezoneOffsetMinutes,
isCurrentLiabilityAccount,
currentAccount,
currentAccountCurrency,
@@ -416,6 +414,7 @@ const {
setReconciliationStatements,
getDisplayDate,
getDisplayTime,
isSameAsDefaultTimezoneOffsetMinutes,
getDisplayTimezone,
getDisplaySourceAmount,
getDisplayDestinationAmount,
@@ -446,8 +445,6 @@ const virtualDataItems = ref<ReconciliationStatementVirtualListData>({
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
const validQuery = computed(() => currentAccount.value && currentAccount.value.type === AccountType.SingleAccount.type);
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 ret: ReconciliationStatementVirtualListItem[] = [];
@@ -124,8 +124,9 @@ import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { TextDirection } from '@/core/text.ts';
import { type DateTime } from '@/core/datetime.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';
const props = defineProps<{
@@ -136,23 +137,23 @@ const {
tt,
getCurrentLanguageTextDirection,
getWeekdayShortName,
getCalendarDisplayDayOfMonthFromUnixTime,
formatUnixTimeToShortTime,
formatUnixTimeToGregorianLikeLongYearMonth,
getCalendarDisplayDayOfMonthFromDateTime,
formatDateTimeToShortTime,
formatDateTimeToGregorianLikeLongYearMonth,
formatAmountToLocalizedNumeralsWithCurrency
} = useI18n();
const settingsStore = useSettingsStore();
const currentUnixTime = ref<number>(getCurrentUnixTime());
const currentDateTime = ref<DateTime>(getCurrentDateTime());
const fontSize = ref<number>(settingsStore.appSettings.fontSize);
const textDirection = computed<string>(() => getCurrentLanguageTextDirection());
const fontSizePreviewClassName = computed<string>(() => getFontSizePreviewClassName(fontSize.value));
const currentLongYearMonth = computed<string>(() => formatUnixTimeToGregorianLikeLongYearMonth(currentUnixTime.value));
const currentDayOfMonth = computed<string>(() => getCalendarDisplayDayOfMonthFromUnixTime(currentUnixTime.value));
const currentDayOfWeek = computed<string>(() => getWeekdayShortName(parseDateTimeFromUnixTime(currentUnixTime.value).getWeekDay()));
const currentShortTime = computed<string>(() => formatUnixTimeToShortTime(currentUnixTime.value));
const currentLongYearMonth = computed<string>(() => formatDateTimeToGregorianLikeLongYearMonth(currentDateTime.value));
const currentDayOfMonth = computed<string>(() => getCalendarDisplayDayOfMonthFromDateTime(currentDateTime.value));
const currentDayOfWeek = computed<string>(() => getWeekdayShortName(currentDateTime.value.getWeekDay()));
const currentShortTime = computed<string>(() => formatDateTimeToShortTime(currentDateTime.value));
function getFontSizeName(): string {
return '';
+20 -13
View File
@@ -251,8 +251,10 @@
</div>
</template>
<date-time-selection-sheet :init-mode="transactionDateTimeSheetMode"
:timezone-utc-offset="transaction.utcOffset"
:model-value="transaction.time"
v-model:show="showTransactionDateTimeSheet"
v-model="transaction.time">
@update:model-value="updateTransactionTime">
</date-time-selection-sheet>
</f7-list-item>
@@ -323,8 +325,9 @@
:filter-placeholder="tt('Timezone')"
:filter-no-items-text="tt('No results')"
:items="allTimezones"
:model-value="transaction.timeZone"
v-model:show="showTimezonePopup"
v-model="transaction.timeZone">
@update:model-value="updateTransactionTimezone">
</list-item-selection-popup>
</f7-list-item>
@@ -512,10 +515,9 @@ import type { TransactionPictureInfoBasicResponse } from '@/models/transaction_p
import { Transaction } from '@/models/transaction.ts';
import {
getActualUnixTimeForStore,
getBrowserTimezoneOffsetMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes
getTimezoneOffsetMinutes,
parseDateTimeFromUnixTimeWithTimezoneOffset
} from '@/lib/datetime.ts';
import { formatCoordinate } from '@/lib/coordinate.ts';
import { generateRandomUUID } from '@/lib/misc.ts';
@@ -536,8 +538,8 @@ const {
tt,
getMultiMonthdayShortNames,
getMultiWeekdayLongNames,
formatUnixTimeToLongDate,
formatUnixTimeToLongTime,
formatDateTimeToLongDate,
formatDateTimeToLongTime,
formatGregorianTextualYearMonthDayToLongDate,
parseAmountFromLocalizedNumerals
} = useI18n();
@@ -589,6 +591,8 @@ const {
geoLocationStatusInfo,
inputEmptyProblemMessage,
inputIsEmpty,
updateTransactionTime,
updateTransactionTimezone,
swapTransactionData,
getDisplayAmount,
getTransactionPictureUrl
@@ -655,19 +659,23 @@ const destinationAmountClass = computed<Record<string, boolean>>(() => {
const transactionDisplayDate = computed<string>(() => {
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>(() => {
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));
return `${formatUnixTimeToLongTime(getActualUnixTimeForStore(transaction.value.time, transaction.value.utcOffset, getBrowserTimezoneOffsetMinutes()))} (UTC${utcOffset})`;
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, getTimezoneOffsetMinutes(transaction.value.time));
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(transaction.value.time));
return `${formatDateTimeToLongTime(dateTime)} (UTC${utcOffset})`;
});
const transactionDisplayTimezoneName = computed<string>(() => {
@@ -949,7 +957,6 @@ function init(): void {
tagIds: query['tagIds'],
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)
);
+8 -9
View File
@@ -205,7 +205,7 @@
<template #media>
<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">
{{ getCalendarDisplayDayOfMonthFromUnixTime(transaction.time) }}
{{ transaction.gregorianCalendarDayOfMonth ? numeralSystem.formatNumber(transaction.gregorianCalendarDayOfMonth) : '' }}
</span>
<span class="transaction-day-of-week full-line flex-direction-column" v-if="transaction.displayDayOfWeek">
{{ getWeekdayShortName(transaction.displayDayOfWeek) }}
@@ -265,7 +265,7 @@
</div>
<div class="transaction-footer">
<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">{{ 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>
@@ -624,7 +624,7 @@ import {
DateRangeScene,
DateRange
} 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 type { TransactionCategory } from '@/models/transaction_category.ts';
import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
@@ -637,8 +637,6 @@ import {
import {
getCurrentUnixTime,
parseDateTimeFromUnixTime,
getBrowserTimezoneOffsetMinutes,
getActualUnixTimeForStore,
getDayFirstUnixTimeBySpecifiedUnixTime,
getYearMonthFirstUnixTime,
getYearMonthLastUnixTime,
@@ -664,8 +662,8 @@ const props = defineProps<{
const {
tt,
getCurrentLanguageTextDirection,
getWeekdayShortName,
getCalendarDisplayDayOfMonthFromUnixTime
getCurrentNumeralSystemType,
getWeekdayShortName
} = useI18n();
const { showAlert, showToast, routeBackOnError } = useI18nUIComponents();
@@ -676,7 +674,6 @@ const {
customMinDatetime,
customMaxDatetime,
currentCalendarDate,
currentTimezoneOffsetMinutes,
firstDayOfWeek,
fiscalYearStart,
defaultCurrency,
@@ -711,6 +708,7 @@ const {
transactionCalendarMaxDate,
currentMonthTransactionData,
hasSubCategoryInQuery,
isSameAsDefaultTimezoneOffsetMinutes,
canAddTransaction,
getDisplayTime,
getDisplayLongYearMonth,
@@ -737,6 +735,7 @@ const showCustomMonthSheet = ref<boolean>(false);
const showDeleteActionSheet = ref<boolean>(false);
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
const transactions = computed<TransactionMonthList[]>(() => {
@@ -1057,7 +1056,7 @@ function changePageType(type: number): void {
function changeDateFilter(dateType: number): void {
if (dateType === DateRange.Custom.type) { // Custom
if (!query.value.minTime || !query.value.maxTime) {
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes());
customMaxDatetime.value = getCurrentUnixTime();
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
} else {
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 { isEquals } from '@/lib/common.ts';
import { parseDateTimeFromUnixTime } from '@/lib/datetime.ts';
import { parseSessionInfo } from '@/lib/session.ts';
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);
this.domId = getTokenDomId(sessionInfo.tokenId);
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;
}>();
const { tt, getCurrentLanguageTextDirection, formatUnixTimeToLongDateTime } = useI18n();
const {
tt,
getCurrentLanguageTextDirection,
formatDateTimeToLongDateTime
} = useI18n();
const { showConfirm, showToast, routeBackOnError } = useI18nUIComponents();
const tokensStore = useTokensStore();