support export to tsv file
This commit is contained in:
+15
-2
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -231,7 +232,7 @@ var UserData = &cli.Command{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "transaction-export",
|
Name: "transaction-export",
|
||||||
Usage: "Export user all transactions to csv file",
|
Usage: "Export user all transactions to file",
|
||||||
Action: exportUserTransaction,
|
Action: exportUserTransaction,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
@@ -246,6 +247,12 @@ var UserData = &cli.Command{
|
|||||||
Required: true,
|
Required: true,
|
||||||
Usage: "Specific exported file path (e.g. transaction.csv)",
|
Usage: "Specific exported file path (e.g. transaction.csv)",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Required: false,
|
||||||
|
Usage: "Export file type, support csv or tsv, default is csv",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -555,6 +562,12 @@ func exportUserTransaction(c *cli.Context) error {
|
|||||||
|
|
||||||
username := c.String("username")
|
username := c.String("username")
|
||||||
filePath := c.String("file")
|
filePath := c.String("file")
|
||||||
|
fileType := c.String("type")
|
||||||
|
|
||||||
|
if fileType != "" && fileType != "csv" && fileType != "tsv" {
|
||||||
|
log.BootErrorf("[user_data.exportUserTransaction] export file type is not supported")
|
||||||
|
return errs.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
log.BootErrorf("[user_data.exportUserTransaction] export file path is not specified")
|
log.BootErrorf("[user_data.exportUserTransaction] export file path is not specified")
|
||||||
@@ -570,7 +583,7 @@ func exportUserTransaction(c *cli.Context) error {
|
|||||||
|
|
||||||
log.BootInfof("[user_data.exportUserTransaction] starting exporting user \"%s\" data", username)
|
log.BootInfof("[user_data.exportUserTransaction] starting exporting user \"%s\" data", username)
|
||||||
|
|
||||||
content, err := clis.UserData.ExportTransaction(c, username)
|
content, err := clis.UserData.ExportTransaction(c, username, fileType)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.exportUserTransaction] error occurs when exporting user data")
|
log.BootErrorf("[user_data.exportUserTransaction] error occurs when exporting user data")
|
||||||
|
|||||||
+15
-1
@@ -256,7 +256,8 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
|
apiV1Route.POST("/data/clear.json", bindApi(api.DataManagements.ClearDataHandler))
|
||||||
|
|
||||||
if config.EnableDataExport {
|
if config.EnableDataExport {
|
||||||
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
|
apiV1Route.GET("/data/export.csv", bindCsv(api.DataManagements.ExportDataToEzbookkeepingCSVHandler))
|
||||||
|
apiV1Route.GET("/data/export.tsv", bindTsv(api.DataManagements.ExportDataToEzbookkeepingTSVHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
@@ -376,6 +377,19 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bindTsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
||||||
|
return func(ginCtx *gin.Context) {
|
||||||
|
c := core.WrapContext(ginCtx)
|
||||||
|
result, fileName, err := fn(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.PrintDataErrorResult(c, "text/text", err)
|
||||||
|
} else {
|
||||||
|
utils.PrintDataSuccessResult(c, "text/tab-separated-values", fileName, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func bindCachedPngImage(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc {
|
func bindCachedPngImage(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc {
|
||||||
return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) {
|
return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) {
|
||||||
c := core.WrapContext(ginCtx)
|
c := core.WrapContext(ginCtx)
|
||||||
|
|||||||
+109
-90
@@ -19,103 +19,38 @@ const pageCountForDataExport = 1000
|
|||||||
|
|
||||||
// DataManagementsApi represents data management api
|
// DataManagementsApi represents data management api
|
||||||
type DataManagementsApi struct {
|
type DataManagementsApi struct {
|
||||||
exporter *converters.EzBookKeepingCSVFileExporter
|
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
||||||
tokens *services.TokenService
|
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileExporter
|
||||||
users *services.UserService
|
tokens *services.TokenService
|
||||||
accounts *services.AccountService
|
users *services.UserService
|
||||||
transactions *services.TransactionService
|
accounts *services.AccountService
|
||||||
categories *services.TransactionCategoryService
|
transactions *services.TransactionService
|
||||||
tags *services.TransactionTagService
|
categories *services.TransactionCategoryService
|
||||||
|
tags *services.TransactionTagService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a data management api singleton instance
|
// Initialize a data management api singleton instance
|
||||||
var (
|
var (
|
||||||
DataManagements = &DataManagementsApi{
|
DataManagements = &DataManagementsApi{
|
||||||
exporter: &converters.EzBookKeepingCSVFileExporter{},
|
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
||||||
tokens: services.Tokens,
|
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileExporter{},
|
||||||
users: services.Users,
|
tokens: services.Tokens,
|
||||||
accounts: services.Accounts,
|
users: services.Users,
|
||||||
transactions: services.Transactions,
|
accounts: services.Accounts,
|
||||||
categories: services.TransactionCategories,
|
transactions: services.Transactions,
|
||||||
tags: services.TransactionTags,
|
categories: services.TransactionCategories,
|
||||||
|
tags: services.TransactionTags,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExportDataHandler returns exported data in csv format
|
// ExportDataToEzbookkeepingCSVHandler returns exported data in csv format
|
||||||
func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
func (a *DataManagementsApi) ExportDataToEzbookkeepingCSVHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||||
if !settings.Container.Current.EnableDataExport {
|
return a.getExportedFileContent(c, "csv")
|
||||||
return nil, "", errs.ErrDataExportNotAllowed
|
}
|
||||||
}
|
|
||||||
|
|
||||||
timezone := time.Local
|
// ExportDataToEzbookkeepingTSVHandler returns exported data in csv format
|
||||||
utcOffset, err := c.GetClientTimezoneOffset()
|
func (a *DataManagementsApi) ExportDataToEzbookkeepingTSVHandler(c *core.Context) ([]byte, string, *errs.Error) {
|
||||||
|
return a.getExportedFileContent(c, "tsv")
|
||||||
if err != nil {
|
|
||||||
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
|
||||||
} else {
|
|
||||||
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
|
||||||
user, err := a.users.GetUserById(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !errs.IsCustomError(err) {
|
|
||||||
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, "", errs.ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
tagIndexs, err := a.tags.GetAllTagIdsOfAllTransactions(c, uid)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
accountMap := a.accounts.GetAccountMapByList(accounts)
|
|
||||||
categoryMap := a.categories.GetCategoryMapByList(categories)
|
|
||||||
tagMap := a.tags.GetTagMapByList(tags)
|
|
||||||
|
|
||||||
allTransactions, err := a.transactions.GetAllTransactions(c, uid, pageCountForDataExport, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := a.exporter.ToExportedContent(uid, timezone, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
|
||||||
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := a.getFileName(user, timezone)
|
|
||||||
|
|
||||||
return result, fileName, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataStatisticsHandler returns user data statistics
|
// DataStatisticsHandler returns user data statistics
|
||||||
@@ -209,11 +144,95 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (any, *errs.Error
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *DataManagementsApi) getFileName(user *models.User, timezone *time.Location) string {
|
func (a *DataManagementsApi) getExportedFileContent(c *core.Context, fileType string) ([]byte, string, *errs.Error) {
|
||||||
|
if !settings.Container.Current.EnableDataExport {
|
||||||
|
return nil, "", errs.ErrDataExportNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone := time.Local
|
||||||
|
utcOffset, err := c.GetClientTimezoneOffset()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
|
||||||
|
} else {
|
||||||
|
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsCustomError(err) {
|
||||||
|
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errs.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := a.accounts.GetAllAccountsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err := a.categories.GetAllCategoriesByUid(c, uid, 0, -1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := a.tags.GetAllTagsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndexs, err := a.tags.GetAllTagIdsOfAllTransactions(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get tag index for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
accountMap := a.accounts.GetAccountMapByList(accounts)
|
||||||
|
categoryMap := a.categories.GetCategoryMapByList(categories)
|
||||||
|
tagMap := a.tags.GetTagMapByList(tags)
|
||||||
|
|
||||||
|
allTransactions, err := a.transactions.GetAllTransactions(c, uid, pageCountForDataExport, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to all transactions user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataExporter converters.DataConverter
|
||||||
|
|
||||||
|
if fileType == "tsv" {
|
||||||
|
dataExporter = a.ezBookKeepingTsvExporter
|
||||||
|
} else {
|
||||||
|
dataExporter = a.ezBookKeepingCsvExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := dataExporter.ToExportedContent(uid, timezone, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, "", errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := a.getFileName(user, timezone, 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)
|
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
||||||
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
||||||
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
||||||
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
||||||
|
|
||||||
return fmt.Sprintf("%s_%s.csv", user.Username, currentTime)
|
return fmt.Sprintf("%s_%s.%s", user.Username, currentTime, fileExtension)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -20,6 +20,7 @@ const pageCountForDataExport = 1000
|
|||||||
// UserDataCli represents user data cli
|
// UserDataCli represents user data cli
|
||||||
type UserDataCli struct {
|
type UserDataCli struct {
|
||||||
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileExporter
|
||||||
|
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileExporter
|
||||||
accounts *services.AccountService
|
accounts *services.AccountService
|
||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
categories *services.TransactionCategoryService
|
categories *services.TransactionCategoryService
|
||||||
@@ -34,6 +35,7 @@ type UserDataCli struct {
|
|||||||
var (
|
var (
|
||||||
UserData = &UserDataCli{
|
UserData = &UserDataCli{
|
||||||
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileExporter{},
|
||||||
|
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileExporter{},
|
||||||
accounts: services.Accounts,
|
accounts: services.Accounts,
|
||||||
transactions: services.Transactions,
|
transactions: services.Transactions,
|
||||||
categories: services.TransactionCategories,
|
categories: services.TransactionCategories,
|
||||||
@@ -537,7 +539,7 @@ func (l *UserDataCli) CheckTransactionAndAccount(c *cli.Context, username string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExportTransaction returns csv file content according user all transactions
|
// ExportTransaction returns csv file content according user all transactions
|
||||||
func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte, error) {
|
func (l *UserDataCli) ExportTransaction(c *cli.Context, username string, fileType string) ([]byte, error) {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
log.BootErrorf("[user_data.ExportTransaction] user name is empty")
|
log.BootErrorf("[user_data.ExportTransaction] user name is empty")
|
||||||
return nil, errs.ErrUsernameIsEmpty
|
return nil, errs.ErrUsernameIsEmpty
|
||||||
@@ -564,7 +566,15 @@ func (l *UserDataCli) ExportTransaction(c *cli.Context, username string) ([]byte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := l.ezBookKeepingCsvExporter.ToExportedContent(uid, time.Local, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
var dataExporter converters.DataConverter
|
||||||
|
|
||||||
|
if fileType == "tsv" {
|
||||||
|
dataExporter = l.ezBookKeepingTsvExporter
|
||||||
|
} else {
|
||||||
|
dataExporter = l.ezBookKeepingCsvExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := dataExporter.ToExportedContent(uid, time.Local, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"%s\", because %s", username, err.Error())
|
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"%s\", because %s", username, err.Error())
|
||||||
|
|||||||
@@ -1,179 +1,17 @@
|
|||||||
package converters
|
package converters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EzBookKeepingCSVFileExporter defines the structure of csv file exporter
|
// EzBookKeepingCSVFileExporter defines the structure of CSV file exporter
|
||||||
type EzBookKeepingCSVFileExporter struct {
|
type EzBookKeepingCSVFileExporter struct {
|
||||||
DataConverter
|
EzBookKeepingPlainFileExporter
|
||||||
}
|
}
|
||||||
|
|
||||||
const csvHeaderLine = "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Tags,Comment\n"
|
// ToExportedContent returns the exported CSV data
|
||||||
const csvDataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"
|
|
||||||
|
|
||||||
// ToExportedContent returns the exported csv data
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) ToExportedContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
|
func (e *EzBookKeepingCSVFileExporter) ToExportedContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
|
||||||
var ret strings.Builder
|
return e.toExportedContent(uid, ",", timezone, transactions, accountMap, categoryMap, tagMap, allTagIndexs)
|
||||||
|
|
||||||
ret.Grow(len(transactions) * 100)
|
|
||||||
ret.WriteString(csvHeaderLine)
|
|
||||||
|
|
||||||
for i := 0; i < len(transactions); i++ {
|
|
||||||
transaction := transactions[i]
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
|
||||||
transactionTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
|
||||||
transactionTimezone := utils.FormatTimezoneOffset(transactionTimeZone)
|
|
||||||
transactionType := e.getTransactionTypeName(transaction.Type)
|
|
||||||
category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap)
|
|
||||||
subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap)
|
|
||||||
account := e.getAccountName(transaction.AccountId, accountMap)
|
|
||||||
accountCurrency := e.getAccountCurrency(transaction.AccountId, accountMap)
|
|
||||||
amount := e.getDisplayAmount(transaction.Amount)
|
|
||||||
account2 := ""
|
|
||||||
account2Currency := ""
|
|
||||||
account2Amount := ""
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
|
||||||
account2 = e.getAccountName(transaction.RelatedAccountId, accountMap)
|
|
||||||
account2Currency = e.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
|
||||||
account2Amount = e.getDisplayAmount(transaction.RelatedAccountAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := e.getTags(transaction.TransactionId, allTagIndexs, tagMap)
|
|
||||||
comment := e.getComment(transaction.Comment)
|
|
||||||
|
|
||||||
ret.WriteString(fmt.Sprintf(csvDataLineFormat, transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, tags, comment))
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(ret.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
|
||||||
if transactionDbType == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
|
||||||
return "Balance Modification"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME {
|
|
||||||
return "Income"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
|
||||||
return "Expense"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
return "Transfer"
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if category.ParentCategoryId == 0 {
|
|
||||||
return category.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentCategory.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return category.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Currency
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getDisplayAmount(amount int64) string {
|
|
||||||
displayAmount := utils.Int64ToString(amount)
|
|
||||||
integer := utils.SubString(displayAmount, 0, len(displayAmount)-2)
|
|
||||||
decimals := utils.SubString(displayAmount, -2, 2)
|
|
||||||
|
|
||||||
if integer == "" {
|
|
||||||
integer = "0"
|
|
||||||
} else if integer == "-" {
|
|
||||||
integer = "-0"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decimals) == 0 {
|
|
||||||
decimals = "00"
|
|
||||||
} else if len(decimals) == 1 {
|
|
||||||
decimals = "0" + decimals
|
|
||||||
}
|
|
||||||
|
|
||||||
return integer + "." + decimals
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getTags(transactionId int64, allTagIndexs map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
|
||||||
tagIndexs, exists := allTagIndexs[transactionId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret strings.Builder
|
|
||||||
|
|
||||||
for i := 0; i < len(tagIndexs); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
ret.WriteString(";")
|
|
||||||
}
|
|
||||||
|
|
||||||
tagIndex := tagIndexs[i]
|
|
||||||
tag, exists := tagMap[tagIndex]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.WriteString(tag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingCSVFileExporter) getComment(comment string) string {
|
|
||||||
comment = strings.Replace(comment, ",", " ", -1)
|
|
||||||
comment = strings.Replace(comment, "\r\n", " ", -1)
|
|
||||||
comment = strings.Replace(comment, "\n", " ", -1)
|
|
||||||
|
|
||||||
return comment
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EzBookKeepingPlainFileExporter defines the structure of plain file exporter
|
||||||
|
type EzBookKeepingPlainFileExporter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerLine = "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Tags,Comment\n"
|
||||||
|
const dataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"
|
||||||
|
|
||||||
|
// toExportedContent returns the exported plain data
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) toExportedContent(uid int64, separator string, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
ret.Grow(len(transactions) * 100)
|
||||||
|
|
||||||
|
if separator == "," {
|
||||||
|
ret.WriteString(headerLine)
|
||||||
|
} else {
|
||||||
|
ret.WriteString(strings.Replace(headerLine, ",", separator, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(transactions); i++ {
|
||||||
|
transaction := transactions[i]
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
|
transactionTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||||
|
transactionTimezone := utils.FormatTimezoneOffset(transactionTimeZone)
|
||||||
|
transactionType := e.getTransactionTypeName(transaction.Type)
|
||||||
|
category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap)
|
||||||
|
subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap)
|
||||||
|
account := e.getAccountName(transaction.AccountId, accountMap)
|
||||||
|
accountCurrency := e.getAccountCurrency(transaction.AccountId, accountMap)
|
||||||
|
amount := e.getDisplayAmount(transaction.Amount)
|
||||||
|
account2 := ""
|
||||||
|
account2Currency := ""
|
||||||
|
account2Amount := ""
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
account2 = e.getAccountName(transaction.RelatedAccountId, accountMap)
|
||||||
|
account2Currency = e.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
||||||
|
account2Amount = e.getDisplayAmount(transaction.RelatedAccountAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := e.getTags(transaction.TransactionId, allTagIndexs, tagMap)
|
||||||
|
comment := e.getComment(transaction.Comment, separator)
|
||||||
|
|
||||||
|
if separator == "," {
|
||||||
|
ret.WriteString(fmt.Sprintf(dataLineFormat, transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, tags, comment))
|
||||||
|
} else {
|
||||||
|
ret.WriteString(fmt.Sprintf(strings.Replace(dataLineFormat, ",", separator, -1), transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, tags, comment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(ret.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
||||||
|
if transactionDbType == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
|
return "Balance Modification"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME {
|
||||||
|
return "Income"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
|
return "Expense"
|
||||||
|
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
return "Transfer"
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.ParentCategoryId == 0 {
|
||||||
|
return category.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentCategory.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return category.Name
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return account.Name
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return account.Currency
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getDisplayAmount(amount int64) string {
|
||||||
|
displayAmount := utils.Int64ToString(amount)
|
||||||
|
integer := utils.SubString(displayAmount, 0, len(displayAmount)-2)
|
||||||
|
decimals := utils.SubString(displayAmount, -2, 2)
|
||||||
|
|
||||||
|
if integer == "" {
|
||||||
|
integer = "0"
|
||||||
|
} else if integer == "-" {
|
||||||
|
integer = "-0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decimals) == 0 {
|
||||||
|
decimals = "00"
|
||||||
|
} else if len(decimals) == 1 {
|
||||||
|
decimals = "0" + decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
return integer + "." + decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getTags(transactionId int64, allTagIndexs map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
||||||
|
tagIndexs, exists := allTagIndexs[transactionId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexs); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
ret.WriteString(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIndex := tagIndexs[i]
|
||||||
|
tag, exists := tagMap[tagIndex]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(tag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EzBookKeepingPlainFileExporter) getComment(comment string, separator string) string {
|
||||||
|
comment = strings.Replace(comment, separator, " ", -1)
|
||||||
|
comment = strings.Replace(comment, "\r\n", " ", -1)
|
||||||
|
comment = strings.Replace(comment, "\n", " ", -1)
|
||||||
|
|
||||||
|
return comment
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EzBookKeepingTSVFileExporter defines the structure of TSV file exporter
|
||||||
|
type EzBookKeepingTSVFileExporter struct {
|
||||||
|
EzBookKeepingPlainFileExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToExportedContent returns the exported TSV data
|
||||||
|
func (e *EzBookKeepingTSVFileExporter) ToExportedContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
|
||||||
|
return e.toExportedContent(uid, "\t", timezone, transactions, accountMap, categoryMap, tagMap, allTagIndexs)
|
||||||
|
}
|
||||||
@@ -9,4 +9,5 @@ var (
|
|||||||
ErrMethodNotAllowed = NewSystemError(SystemSubcategoryDefault, 2, http.StatusMethodNotAllowed, "method not allowed")
|
ErrMethodNotAllowed = NewSystemError(SystemSubcategoryDefault, 2, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
ErrNotImplemented = NewSystemError(SystemSubcategoryDefault, 3, http.StatusNotImplemented, "not implemented")
|
ErrNotImplemented = NewSystemError(SystemSubcategoryDefault, 3, http.StatusNotImplemented, "not implemented")
|
||||||
ErrSystemIsBusy = NewSystemError(SystemSubcategoryDefault, 4, http.StatusNotImplemented, "system is busy")
|
ErrSystemIsBusy = NewSystemError(SystemSubcategoryDefault, 4, http.StatusNotImplemented, "system is busy")
|
||||||
|
ErrNotSupported = NewSystemError(SystemSubcategoryDefault, 5, http.StatusBadRequest, "not supported")
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-2
@@ -214,8 +214,14 @@ export default {
|
|||||||
getUserDataStatistics: () => {
|
getUserDataStatistics: () => {
|
||||||
return axios.get('v1/data/statistics.json');
|
return axios.get('v1/data/statistics.json');
|
||||||
},
|
},
|
||||||
getExportedUserData: () => {
|
getExportedUserData: (fileType) => {
|
||||||
return axios.get('v1/data/export.csv');
|
if (fileType === 'csv') {
|
||||||
|
return axios.get('v1/data/export.csv');
|
||||||
|
} else if (fileType === 'tsv') {
|
||||||
|
return axios.get('v1/data/export.tsv');
|
||||||
|
} else {
|
||||||
|
return Promise.reject('Parameter Invalid');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clearData: ({ password }) => {
|
clearData: ({ password }) => {
|
||||||
return axios.post('v1/data/clear.json', {
|
return axios.post('v1/data/clear.json', {
|
||||||
|
|||||||
+5
-2
@@ -560,6 +560,7 @@ export default {
|
|||||||
'api not found': 'Failed to request api',
|
'api not found': 'Failed to request api',
|
||||||
'not implemented': 'Not implemented',
|
'not implemented': 'Not implemented',
|
||||||
'system is busy': 'System is busy',
|
'system is busy': 'System is busy',
|
||||||
|
'not supported': 'Not supported',
|
||||||
'database operation failed': 'Database operation failed',
|
'database operation failed': 'Database operation failed',
|
||||||
'SMTP server is not enabled': 'SMTP server is not enabled',
|
'SMTP server is not enabled': 'SMTP server is not enabled',
|
||||||
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
|
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
|
||||||
@@ -1087,9 +1088,11 @@ export default {
|
|||||||
'Data Management': 'Data Management',
|
'Data Management': 'Data Management',
|
||||||
'Unable to get user statistics data': 'Unable to get user statistics data',
|
'Unable to get user statistics data': 'Unable to get user statistics data',
|
||||||
'Export Data': 'Export Data',
|
'Export Data': 'Export Data',
|
||||||
|
'Export Data To CSV File': 'Export Data To CSV File',
|
||||||
|
'Export Data To TSV File': 'Export Data To TSV File',
|
||||||
'Clear User Data': 'Clear User Data',
|
'Clear User Data': 'Clear User Data',
|
||||||
'Export all data to csv file.': 'Export all data to csv file.',
|
'Export all data to file.': 'Export all data to file.',
|
||||||
'Are you sure you want to export all data to csv file?': 'Are you sure you want to export all data to csv file?',
|
'Are you sure you want to export all data to file?': 'Are you sure you want to export all data to file?',
|
||||||
'It may take a long time, please wait for a few minutes.': 'It may take a long time, please wait for a few minutes.',
|
'It may take a long time, please wait for a few minutes.': 'It may take a long time, please wait for a few minutes.',
|
||||||
'Unable to get exported user data': 'Unable to get exported user data',
|
'Unable to get exported user data': 'Unable to get exported user data',
|
||||||
'Save Data': 'Save Data',
|
'Save Data': 'Save Data',
|
||||||
|
|||||||
@@ -560,6 +560,7 @@ export default {
|
|||||||
'api not found': '接口调用失败',
|
'api not found': '接口调用失败',
|
||||||
'not implemented': '未实现',
|
'not implemented': '未实现',
|
||||||
'system is busy': '系统繁忙',
|
'system is busy': '系统繁忙',
|
||||||
|
'not supported': '不支持',
|
||||||
'database operation failed': '数据库操作失败',
|
'database operation failed': '数据库操作失败',
|
||||||
'SMTP server is not enabled': 'SMTP 服务器没有启用',
|
'SMTP server is not enabled': 'SMTP 服务器没有启用',
|
||||||
'incomplete or incorrect submission': '提交不完整或不正确',
|
'incomplete or incorrect submission': '提交不完整或不正确',
|
||||||
@@ -1087,9 +1088,11 @@ export default {
|
|||||||
'Data Management': '数据管理',
|
'Data Management': '数据管理',
|
||||||
'Unable to get user statistics data': '无法获取用户统计数据',
|
'Unable to get user statistics data': '无法获取用户统计数据',
|
||||||
'Export Data': '导出数据',
|
'Export Data': '导出数据',
|
||||||
|
'Export Data To CSV File': '导出数据到 CSV 文件',
|
||||||
|
'Export Data To TSV File': '导出数据到 TSV 文件',
|
||||||
'Clear User Data': '清除用户数据',
|
'Clear User Data': '清除用户数据',
|
||||||
'Export all data to csv file.': '导出所有数据到 csv 文件。',
|
'Export all data to file.': '导出所有数据到文件。',
|
||||||
'Are you sure you want to export all data to csv file?': '您确定要导出所有数据到 csv 文件?',
|
'Are you sure you want to export all data to file?': '您确定要导出所有数据到文件?',
|
||||||
'It may take a long time, please wait for a few minutes.': '这可能花费一些时间,请稍等几分钟。',
|
'It may take a long time, please wait for a few minutes.': '这可能花费一些时间,请稍等几分钟。',
|
||||||
'Unable to get exported user data': '无法获取导出的用户数据',
|
'Unable to get exported user data': '无法获取导出的用户数据',
|
||||||
'Save Data': '保存数据',
|
'Save Data': '保存数据',
|
||||||
|
|||||||
+10
-5
@@ -131,12 +131,17 @@ export const useUserStore = defineStore('user', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getExportedUserData() {
|
getExportedUserData(fileType) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
services.getExportedUserData().then(response => {
|
services.getExportedUserData(fileType).then(response => {
|
||||||
if (response && response.headers && response.headers['content-type'] !== 'text/csv') {
|
if (response && response.headers) {
|
||||||
reject({ message: 'Unable to get exported user data' });
|
if (fileType === 'csv' && response.headers['content-type'] !== 'text/csv') {
|
||||||
return;
|
reject({ message: 'Unable to get exported user data' });
|
||||||
|
return;
|
||||||
|
} else if (fileType === 'tsv' && response.headers['content-type'] !== 'text/tab-separated-values') {
|
||||||
|
reject({ message: 'Unable to get exported user data' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob([response.data], { type: response.headers['content-type'] });
|
const blob = new Blob([response.data], { type: response.headers['content-type'] });
|
||||||
|
|||||||
@@ -85,14 +85,27 @@
|
|||||||
<v-col cols="12" v-if="isDataExportingEnabled">
|
<v-col cols="12" v-if="isDataExportingEnabled">
|
||||||
<v-card :class="{ 'disabled': exportingData }" :title="$t('Export Data')">
|
<v-card :class="{ 'disabled': exportingData }" :title="$t('Export Data')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<span class="text-body-1">{{ $t('Export all data to csv file.') }} {{ $t('It may take a long time, please wait for a few minutes.') }}</span>
|
<span class="text-body-1">{{ $t('Export all data to file.') }} {{ $t('It may take a long time, please wait for a few minutes.') }}</span>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-text class="d-flex flex-wrap gap-4">
|
<v-card-text class="d-flex flex-wrap gap-4">
|
||||||
<v-btn :disabled="loadingDataStatistics || exportingData || !dataStatistics || !dataStatistics.totalTransactionCount || dataStatistics.totalTransactionCount === '0'" @click="exportData">
|
<v-btn-group variant="elevated" density="comfortable" color="primary"
|
||||||
{{ $t('Export Data') }}
|
:disabled="loadingDataStatistics || exportingData || !dataStatistics || !dataStatistics.totalTransactionCount || dataStatistics.totalTransactionCount === '0'">
|
||||||
<v-progress-circular indeterminate size="24" class="ml-2" v-if="exportingData"></v-progress-circular>
|
<v-btn>
|
||||||
</v-btn>
|
{{ $t('Export Data') }}
|
||||||
|
<v-progress-circular indeterminate size="24" class="ml-2" v-if="exportingData"></v-progress-circular>
|
||||||
|
<v-menu activator="parent">
|
||||||
|
<v-list :disabled="loadingDataStatistics || exportingData || !dataStatistics || !dataStatistics.totalTransactionCount || dataStatistics.totalTransactionCount === '0'">
|
||||||
|
<v-list-item @click="exportData('csv')">
|
||||||
|
<v-list-item-title>{{ $t('Export Data To CSV File') }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="exportData('tsv')">
|
||||||
|
<v-list-item-title>{{ $t('Export Data To TSV File') }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-group>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -209,17 +222,6 @@ export default {
|
|||||||
},
|
},
|
||||||
isDataExportingEnabled() {
|
isDataExportingEnabled() {
|
||||||
return isDataExportingEnabled();
|
return isDataExportingEnabled();
|
||||||
},
|
|
||||||
exportFileName() {
|
|
||||||
const nickname = this.userStore.currentUserNickname;
|
|
||||||
|
|
||||||
if (nickname) {
|
|
||||||
return this.$t('dataExport.exportFilename', {
|
|
||||||
nickname: nickname
|
|
||||||
}) + '.csv';
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$t('dataExport.defaultExportFilename') + '.csv';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -250,7 +252,7 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportData() {
|
exportData(fileType) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if (self.exportingData) {
|
if (self.exportingData) {
|
||||||
@@ -259,8 +261,8 @@ export default {
|
|||||||
|
|
||||||
self.exportingData = true;
|
self.exportingData = true;
|
||||||
|
|
||||||
self.userStore.getExportedUserData().then(data => {
|
self.userStore.getExportedUserData(fileType).then(data => {
|
||||||
startDownloadFile(self.exportFileName, data);
|
startDownloadFile(self.getExportFileName(fileType), data);
|
||||||
self.exportingData = false;
|
self.exportingData = false;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
self.exportingData = false;
|
self.exportingData = false;
|
||||||
@@ -301,6 +303,17 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getExportFileName(fileExtension) {
|
||||||
|
const nickname = this.userStore.currentUserNickname;
|
||||||
|
|
||||||
|
if (nickname) {
|
||||||
|
return this.$t('dataExport.exportFilename', {
|
||||||
|
nickname: nickname
|
||||||
|
}) + '.' + fileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.$t('dataExport.defaultExportFilename') + '.' + fileExtension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,19 @@
|
|||||||
<div class="swipe-handler" style="z-index: 10"></div>
|
<div class="swipe-handler" style="z-index: 10"></div>
|
||||||
<f7-page-content class="margin-top no-padding-top">
|
<f7-page-content class="margin-top no-padding-top">
|
||||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||||
<div class="ebk-sheet-title"><b>{{ $t('Are you sure you want to export all data to csv file?') }}</b></div>
|
<div class="ebk-sheet-title"><b>{{ $t('Are you sure you want to export all data to file?') }}</b></div>
|
||||||
|
</div>
|
||||||
|
<div class="padding-bottom padding-horizontal">
|
||||||
|
<f7-list class="export-file-type-list no-margin" dividers>
|
||||||
|
<f7-list-item radio radio-icon="start" :class="{ 'disabled': exportingData || exportedData }"
|
||||||
|
:title="$t('Export Data To CSV File')"
|
||||||
|
:checked="exportFileType === 'csv'" @change="exportFileType = 'csv'">
|
||||||
|
</f7-list-item>
|
||||||
|
<f7-list-item radio radio-icon="start" :class="{ 'disabled': exportingData || exportedData }"
|
||||||
|
:title="$t('Export Data To TSV File')"
|
||||||
|
:checked="exportFileType === 'tsv'" @change="exportFileType = 'tsv'">
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="padding-horizontal padding-bottom">
|
<div class="padding-horizontal padding-bottom">
|
||||||
<p class="no-margin-top margin-bottom-half">{{ $t('It may take a long time, please wait for a few minutes.') }}</p>
|
<p class="no-margin-top margin-bottom-half">{{ $t('It may take a long time, please wait for a few minutes.') }}</p>
|
||||||
@@ -73,6 +85,7 @@ export default {
|
|||||||
loading: true,
|
loading: true,
|
||||||
loadingError: null,
|
loadingError: null,
|
||||||
dataStatistics: null,
|
dataStatistics: null,
|
||||||
|
exportFileType: 'csv',
|
||||||
exportingData: false,
|
exportingData: false,
|
||||||
exportedData: null,
|
exportedData: null,
|
||||||
currentPasswordForClearData: '',
|
currentPasswordForClearData: '',
|
||||||
@@ -109,10 +122,10 @@ export default {
|
|||||||
if (nickname) {
|
if (nickname) {
|
||||||
return this.$t('dataExport.exportFilename', {
|
return this.$t('dataExport.exportFilename', {
|
||||||
nickname: nickname
|
nickname: nickname
|
||||||
}) + '.csv';
|
}) + '.' + this.exportFileType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('dataExport.defaultExportFilename') + '.csv';
|
return this.$t('dataExport.defaultExportFilename') + '.' + this.exportFileType;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -142,7 +155,7 @@ export default {
|
|||||||
self.$showLoading();
|
self.$showLoading();
|
||||||
self.exportingData = true;
|
self.exportingData = true;
|
||||||
|
|
||||||
self.userStore.getExportedUserData().then(data => {
|
self.userStore.getExportedUserData(self.exportFileType).then(data => {
|
||||||
self.exportedData = URL.createObjectURL(data);
|
self.exportedData = URL.createObjectURL(data);
|
||||||
self.exportingData = false;
|
self.exportingData = false;
|
||||||
self.$hideLoading();
|
self.$hideLoading();
|
||||||
@@ -202,3 +215,9 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.export-file-type-list.list > ul > li > .item-content {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user