show process when importing a lot of transactions

This commit is contained in:
MaysWind
2025-05-01 01:00:11 +08:00
parent 20b65fd885
commit b7973772b3
20 changed files with 210 additions and 19 deletions
+1
View File
@@ -319,6 +319,7 @@ func startWebServer(c *core.CliContext) error {
apiV1Route.POST("/transactions/parse_dsv_file.json", bindApi(api.Transactions.TransactionParseImportDsvFileDataHandler))
apiV1Route.POST("/transactions/parse_import.json", bindApi(api.Transactions.TransactionParseImportFileHandler))
apiV1Route.POST("/transactions/import.json", bindApi(api.Transactions.TransactionImportHandler))
apiV1Route.GET("/transactions/import/process.json", bindApi(api.Transactions.TransactionImportProcessHandler))
}
// Transaction Pictures
+69 -6
View File
@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"fmt"
"io"
"sort"
"strings"
@@ -1344,11 +1345,21 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS, uid, transactionImportReq.ClientSessionId)
if found {
log.Infof(c, "[transactions.TransactionImportHandler] another \"%s\" transactions has been imported for user \"uid:%d\"", remark, uid)
count, err := utils.StringToInt(remark)
items := strings.Split(remark, ":")
if err == nil {
return count, nil
if len(items) >= 2 {
if items[0] == "finished" {
log.Infof(c, "[transactions.TransactionImportHandler] another \"%s\" transactions has been imported for user \"uid:%d\"", items[1], uid)
count, err := utils.StringToInt(items[1])
if err == nil {
return count, nil
}
} else if items[0] == "processing" {
return nil, errs.ErrRepeatedRequest
}
} else {
log.Warnf(c, "[transactions.TransactionImportHandler] another transaction import task may be executing, but remark \"%s\" is invalid", remark)
}
}
}
@@ -1422,7 +1433,9 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
newTransactions[i] = transaction
}
err = a.transactions.BatchCreateTransactions(c, user.Uid, newTransactions, newTransactionTagIdsMap)
err = a.transactions.BatchCreateTransactions(c, user.Uid, newTransactions, newTransactionTagIdsMap, func(currentProcess float64) {
a.SetSubmissionRemarkIfEnable(duplicatechecker.DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS, uid, transactionImportReq.ClientSessionId, fmt.Sprintf("processing:%.2f", currentProcess))
})
count := len(newTransactions)
if err != nil {
@@ -1432,11 +1445,61 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
log.Infof(c, "[transactions.TransactionImportHandler] user \"uid:%d\" has imported %d transactions successfully", uid, count)
a.SetSubmissionRemarkIfEnable(duplicatechecker.DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS, uid, transactionImportReq.ClientSessionId, utils.IntToString(count))
a.SetSubmissionRemarkIfEnable(duplicatechecker.DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS, uid, transactionImportReq.ClientSessionId, fmt.Sprintf("finished:%d", count))
return count, nil
}
// TransactionImportProcessHandler returns the process of specified transaction import task by request parameters for current user
func (a *TransactionsApi) TransactionImportProcessHandler(c *core.WebContext) (any, *errs.Error) {
var transactionImportProcessReq models.TransactionImportProcessRequest
err := c.ShouldBindQuery(&transactionImportProcessReq)
if err != nil {
log.Warnf(c, "[transactions.TransactionImportProcessHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
if !a.CurrentConfig().EnableDuplicateSubmissionsCheck {
return nil, nil
}
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_IMPORT_TRANSACTIONS, uid, transactionImportProcessReq.ClientSessionId)
if !found {
return nil, nil
}
items := strings.Split(remark, ":")
if len(items) < 2 {
return nil, nil
}
if items[0] == "finished" {
return 100, nil
} else if items[0] != "processing" {
return nil, nil
}
process, err := utils.StringToFloat64(items[1])
if err != nil {
log.Warnf(c, "[transactions.TransactionImportProcessHandler] parse process failed, because %s", err.Error())
return nil, nil
}
if process < 0 {
return nil, nil
} else if process >= 100 {
process = 100
}
return process, nil
}
func (a *TransactionsApi) filterTransactions(c *core.WebContext, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account) []*models.Transaction {
finalTransactions := make([]*models.Transaction, 0, len(transactions))
+1 -1
View File
@@ -810,7 +810,7 @@ func (l *UserDataCli) ImportTransaction(c *core.CliContext, username string, fil
return errs.ErrOperationFailed
}
err = l.transactions.BatchCreateTransactions(c, user.Uid, newTransactions, newTransactionTagIdsMap)
err = l.transactions.BatchCreateTransactions(c, user.Uid, newTransactions, newTransactionTagIdsMap, nil)
if err != nil {
log.CliErrorf(c, "[user_data.ImportTransaction] failed to create transaction, because %s", err.Error())
+4
View File
@@ -0,0 +1,4 @@
package core
// TaskProcessUpdateHandler represents the task process update handler
type TaskProcessUpdateHandler func(currentProcess float64)
+1
View File
@@ -26,6 +26,7 @@ var (
ErrUploadedFileEmpty = NewNormalError(NormalSubcategoryGlobal, 16, http.StatusBadRequest, "uploaded file is empty")
ErrExceedMaxUploadFileSize = NewNormalError(NormalSubcategoryGlobal, 17, http.StatusBadRequest, "uploaded file size exceeds the maximum allowed size")
ErrFailureCountLimitReached = NewNormalError(NormalSubcategoryGlobal, 18, http.StatusBadRequest, "failure count exceeded maximum limit")
ErrRepeatedRequest = NewNormalError(NormalSubcategoryGlobal, 19, http.StatusBadRequest, "repeated request")
)
// GetParameterInvalidMessage returns specific error message for invalid parameter error
+5
View File
@@ -149,6 +149,11 @@ type TransactionImportRequest struct {
ClientSessionId string `json:"clientSessionId"`
}
// TransactionImportProcessRequest represents all parameters of transaction import process request
type TransactionImportProcessRequest struct {
ClientSessionId string `form:"client_session_id"`
}
// TransactionCountRequest represents transaction count request
type TransactionCountRequest struct {
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
+11 -1
View File
@@ -2,6 +2,7 @@ package services
import (
"fmt"
"math"
"strings"
"time"
@@ -260,8 +261,11 @@ func (s *TransactionService) CreateTransaction(c core.Context, transaction *mode
}
// BatchCreateTransactions saves new transactions to database
func (s *TransactionService) BatchCreateTransactions(c core.Context, uid int64, transactions []*models.Transaction, allTagIds map[int][]int64) error {
func (s *TransactionService) BatchCreateTransactions(c core.Context, uid int64, transactions []*models.Transaction, allTagIds map[int][]int64, processHandler core.TaskProcessUpdateHandler) error {
now := time.Now().Unix()
currentProcess := float64(0)
processUpdateStep := int(math.Max(100.0, float64(len(transactions)/100.0)))
needTransactionUuidCount := uint16(0)
needTagIndexUuidCount := uint16(0)
@@ -366,6 +370,12 @@ func (s *TransactionService) BatchCreateTransactions(c core.Context, uid int64,
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
err := s.doCreateTransaction(c, userDataDb, sess, transaction, transactionTagIndexes, transactionTagIds, nil, nil)
currentProcess = float64(i) / float64(len(transactions)) * 100
if processHandler != nil && i%processUpdateStep == 0 {
processHandler(currentProcess)
}
if err != nil {
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime)
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
+5
View File
@@ -483,6 +483,11 @@ export default {
timeout: DEFAULT_IMPORT_API_TIMEOUT
} as ApiRequestConfig);
},
getImportTransactionsProcess: (clientSessionId: string): ApiResponsePromise<number | null> => {
return axios.get<ApiResponse<number | null>>('v1/transactions/import/process.json?client_session_id=' + clientSessionId, {
ignoreError: true
} as ApiRequestConfig);
},
uploadTransactionPicture: ({ pictureFile, clientSessionId }: { pictureFile: File, clientSessionId?: string }): ApiResponsePromise<TransactionPictureInfoBasicResponse> => {
return axios.postForm<ApiResponse<TransactionPictureInfoBasicResponse>>('v1/transaction/pictures/upload.json', {
picture: pictureFile,
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "{count} von {totalCount} ausgewählt",
"youHaveUpdatedTransactions": "Sie haben {count} Transaktionen aktualisiert",
"confirmImportTransactions": "Sind Sie sicher, dass Sie {count} Transaktionen importieren möchten?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Sie haben {count} Transaktionen erfolgreich importiert.",
"accountActivationAndResendValidationEmailTip": "Ein Aktivierungslink wurde an Ihre E-Mail-Adresse gesendet: {email}. Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail erneut zu senden.",
"resendValidationEmailTip": "Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail an: {email} erneut zu senden."
@@ -1179,7 +1180,8 @@
"no files uploaded": "Keine Dateien hochgeladen",
"uploaded file is empty": "Hochgeladene Datei ist leer",
"uploaded file size exceeds the maximum allowed size": "Hochgeladene Datei überschreitet die maximal zulässige Größe",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "Ungültige Transaktionen können nicht importiert werden",
"Unable to parse import file": "Importdatei kann nicht geparst werden",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Ausgewählte Ausgabenkategorien im Batch ersetzen",
"Batch Replace Selected Income Categories": "Ausgewählte Einnahmenkategorien im Batch ersetzen",
"Batch Replace Selected Transfer Categories": "Ausgewählte Überweisungskategorien im Batch ersetzen",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "Selected {count} of {totalCount}",
"youHaveUpdatedTransactions": "You have updated {count} transactions",
"confirmImportTransactions": "Are you sure you want to import {count} transactions?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "You have imported {count} transactions successfully.",
"accountActivationAndResendValidationEmailTip": "Account activation link has been sent to your email address: {email}, If you don't receive the mail, please fill password again and click the button below to resend the validation mail.",
"resendValidationEmailTip": "If you don't receive the mail, please fill password again and click the button below to resend the validation mail to: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "No files uploaded",
"uploaded file is empty": "Uploaded file is empty",
"uploaded file size exceeds the maximum allowed size": "Uploaded file size exceeds the maximum allowed size",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "Cannot import invalid transactions",
"Unable to parse import file": "Unable to parse import file",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Batch Replace Selected Expense Categories",
"Batch Replace Selected Income Categories": "Batch Replace Selected Income Categories",
"Batch Replace Selected Transfer Categories": "Batch Replace Selected Transfer Categories",
+4 -1
View File
@@ -101,6 +101,7 @@
"selectedCount": "Seleccionado {count} de {totalCount}",
"youHaveUpdatedTransactions": "Has actualizado {count} transacciones",
"confirmImportTransactions": "¿Está seguro de que desea importar {count} transacciones?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Ha importado {count} transacciones correctamente.",
"accountActivationAndResendValidationEmailTip": "El enlace de activación de la cuenta se envió a su dirección de correo electrónico: {email}. Si no recibe el correo, ingrese la contraseña nuevamente y haga clic en el botón a continuación para reenviar el correo de validación.",
"resendValidationEmailTip": "Si no recibe el correo, complete nuevamente la contraseña y haga clic en el botón a continuación para reenviar el correo de validación a: {email}"
@@ -1178,7 +1179,8 @@
"no files uploaded": "No se subieron archivos",
"uploaded file is empty": "El archivo subido está vacío",
"uploaded file size exceeds the maximum allowed size": "El tamaño del archivo cargado excede el tamaño máximo permitido",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "IDENTIFICACIÓN",
@@ -1688,6 +1690,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "No se pueden importar transacciones no válidas",
"Unable to parse import file": "No se puede analizar el archivo de importación",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Reemplazar por lotes categorías de gastos seleccionadas",
"Batch Replace Selected Income Categories": "Reemplazo por lotes de categorías de ingresos seleccionadas",
"Batch Replace Selected Transfer Categories": "Reemplazar por lotes las categorías de transferencia seleccionadas",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "{count} selezionati su {totalCount}",
"youHaveUpdatedTransactions": "Hai aggiornato {count} transazioni",
"confirmImportTransactions": "Sei sicuro di voler importare {count} transazioni?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Hai importato {count} transazioni.",
"accountActivationAndResendValidationEmailTip": "Abbiamo inviato un link per l'attivazione del tuo account all'indirizzo {email}. Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio.",
"resendValidationEmailTip": "Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio all'indirizzo: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "Nessun file caricato",
"uploaded file is empty": "Il file caricato è vuoto",
"uploaded file size exceeds the maximum allowed size": "La dimensione del file caricato supera la dimensione massima consentita",
"failure count exceeded maximum limit": "Il conteggio dei fallimenti ha superato il limite massimo, riprova più tardi"
"failure count exceeded maximum limit": "Il conteggio dei fallimenti ha superato il limite massimo, riprova più tardi",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Formato importo transazione non impostato",
"Cannot import invalid transactions": "Impossibile importare transazioni non valide",
"Unable to parse import file": "Impossibile analizzare il file di importazione",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Sostituisci in blocco categorie di spesa selezionate",
"Batch Replace Selected Income Categories": "Sostituisci in blocco categorie di entrata selezionate",
"Batch Replace Selected Transfer Categories": "Sostituisci in blocco categorie di trasferimento selezionate",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "{count} / {totalCount}を選択",
"youHaveUpdatedTransactions": "{count}件の取引を更新しました",
"confirmImportTransactions": "本当に{count}件の取引をインポートしますか?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "{count}件の取引を正常にインポートしました。",
"accountActivationAndResendValidationEmailTip": "アカウントの有効化リンクがメールアドレスに送信されました:{email}、メールが届かない場合はパスワードをもう一度入力して下のボタンをクリックして認証メールを再送信してください。",
"resendValidationEmailTip": "メールが届かない場合は、パスワードをもう一度入力の上、以下のボタンをクリックして検証メールを再送信してください: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "アップロードされたファイルはありません",
"uploaded file is empty": "アップロードされたファイルは空です",
"uploaded file size exceeds the maximum allowed size": "アップロードされたファイルが最大許容サイズを超えています",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "無効な取引をインポートできません",
"Unable to parse import file": "インポートファイルを解析できません",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "バッチは選択した支出カテゴリを置き換えます",
"Batch Replace Selected Income Categories": "バッチは選択した収入カテゴリを置き換えます",
"Batch Replace Selected Transfer Categories": "バッチは選択した振替カテゴリを置き換えます",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "Выбрано {count} из {totalCount}",
"youHaveUpdatedTransactions": "Вы обновили {count} транзакций",
"confirmImportTransactions": "Вы уверены, что хотите импортировать {count} транзакций?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Вы успешно импортировали {count} транзакций.",
"accountActivationAndResendValidationEmailTip": "Ссылка для активации учетной записи была отправлена на ваш электронный адрес: {email}. Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно.",
"resendValidationEmailTip": "Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно на: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "Файлы не загружены",
"uploaded file is empty": "Загруженный файл пуст",
"uploaded file size exceeds the maximum allowed size": "Размер загруженного файла превышает максимально допустимый размер",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "Невозможно импортировать недействительные транзакции",
"Unable to parse import file": "Не удалось обработать файл импорта",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов",
"Batch Replace Selected Income Categories": "Пакетная замена выбранных категорий доходов",
"Batch Replace Selected Transfer Categories": "Пакетная замена выбранных категорий переводов",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "Вибрано {count} з {totalCount}",
"youHaveUpdatedTransactions": "Ви оновили {count} транзакцій",
"confirmImportTransactions": "Ви впевнені, що хочете імпортувати {count} транзакцій?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Ви успішно імпортували {count} транзакцій.",
"accountActivationAndResendValidationEmailTip": "Посилання для активації облікового запису було надіслано на вашу електронну адресу: {email}. Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно.",
"resendValidationEmailTip": "Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно на адресу: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "Файли не завантажено",
"uploaded file is empty": "Завантажений файл порожній",
"uploaded file size exceeds the maximum allowed size": "Розмір завантаженого файлу перевищує максимально допустимий",
"failure count exceeded maximum limit": "Кількість невдали спроб перевищила допустимий ліміт, спробуйте пізніше"
"failure count exceeded maximum limit": "Кількість невдали спроб перевищила допустимий ліміт, спробуйте пізніше",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Не вказано формат суми транзакцій",
"Cannot import invalid transactions": "Неможливо імпортувати недійсні транзакції",
"Unable to parse import file": "Не вдалося обробити файл імпорту",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Пакетна заміна вибраних категорій витрат",
"Batch Replace Selected Income Categories": "Пакетна заміна вибраних категорій доходів",
"Batch Replace Selected Transfer Categories": "Пакетна заміна вибраних категорій переказів",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "Đã chọn {count} trên {totalCount}",
"youHaveUpdatedTransactions": "Bạn đã cập nhật {count} giao dịch",
"confirmImportTransactions": "Bạn có chắc chắn muốn nhập {count} giao dịch không?",
"importingTransactions": "Importing ({process}%)",
"importTransactionResult": "Bạn đã nhập thành công {count} giao dịch.",
"accountActivationAndResendValidationEmailTip": "Liên kết kích hoạt tài khoản đã được gửi tới email của bạn: {email}. Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận.",
"resendValidationEmailTip": "Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận tới: {email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "Không có tệp nào được tải lên",
"uploaded file is empty": "Tệp đã tải lên trống",
"uploaded file size exceeds the maximum allowed size": "Kích thước tệp đã tải lên vượt quá kích thước tối đa cho phép",
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time"
"failure count exceeded maximum limit": "Failure count exceeded maximum limit, please try again after some time",
"repeated request": "Repeated Request"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "Transaction amount format is not set",
"Cannot import invalid transactions": "Không thể nhập giao dịch không hợp lệ",
"Unable to parse import file": "Không thể phân tích tệp nhập",
"Unable to import transactions": "Unable to import transactions",
"Batch Replace Selected Expense Categories": "Thay thế hàng loạt các danh mục chi phí đã chọn",
"Batch Replace Selected Income Categories": "Thay thế hàng loạt các danh mục thu nhập đã chọn",
"Batch Replace Selected Transfer Categories": "Thay thế hàng loạt các danh mục chuyển khoản đã chọn",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "已选择 {count} / {totalCount}",
"youHaveUpdatedTransactions": "您已经更新 {count} 个交易",
"confirmImportTransactions": "您确定要导入 {count} 个交易?",
"importingTransactions": "正在导入 ({process}%)",
"importTransactionResult": "您已经成功导入 {count} 个交易。",
"accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。",
"resendValidationEmailTip": "如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件到:{email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "没有上传文件",
"uploaded file is empty": "上传的文件为空",
"uploaded file size exceeds the maximum allowed size": "上传的文件大小超出了允许的最大大小",
"failure count exceeded maximum limit": "失败次数超出最大限制,请稍后重试"
"failure count exceeded maximum limit": "失败次数超出最大限制,请稍后重试",
"repeated request": "重复的请求"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "交易金额格式没有设置",
"Cannot import invalid transactions": "不能导入无效的交易",
"Unable to parse import file": "无法解析导入的文件",
"Unable to import transactions": "无法导入交易",
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
"Batch Replace Selected Income Categories": "批量替换选中的收入分类",
"Batch Replace Selected Transfer Categories": "批量替换选中的转账分类",
+4 -1
View File
@@ -102,6 +102,7 @@
"selectedCount": "已選擇 {count} / {totalCount}",
"youHaveUpdatedTransactions": "您已經更新 {count} 個交易",
"confirmImportTransactions": "您確定要匯入 {count} 個交易?",
"importingTransactions": "正在匯入 ({process}%)",
"importTransactionResult": "您已經成功匯入 {count} 個交易。",
"accountActivationAndResendValidationEmailTip": "帳號啟用連結已經傳送到您的信箱地址:{email},如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件。",
"resendValidationEmailTip": "如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件到:{email}"
@@ -1179,7 +1180,8 @@
"no files uploaded": "沒有上傳檔案",
"uploaded file is empty": "上傳的檔案為空",
"uploaded file size exceeds the maximum allowed size": "上傳的檔案大小超出了允許的最大大小",
"failure count exceeded maximum limit": "失敗次數超出最大限制,請稍後重試"
"failure count exceeded maximum limit": "失敗次數超出最大限制,請稍後重試",
"repeated request": "重複的請求"
},
"parameter": {
"id": "ID",
@@ -1689,6 +1691,7 @@
"Transaction amount format is not set": "交易金額格式沒有設定",
"Cannot import invalid transactions": "無法匯入無效的交易",
"Unable to parse import file": "無法解析匯入的檔案",
"Unable to import transactions": "無法匯入交易",
"Batch Replace Selected Expense Categories": "批次替換選中的支出分類",
"Batch Replace Selected Income Categories": "批次替換選中的收入分類",
"Batch Replace Selected Transfer Categories": "批次替換選中的轉帳分類",
+26
View File
@@ -1143,6 +1143,31 @@ export const useTransactionsStore = defineStore('transactions', () => {
});
}
function getImportTransactionsProcess({ clientSessionId }: { clientSessionId: string }): Promise<number | null> {
return new Promise((resolve, reject) => {
services.getImportTransactionsProcess(clientSessionId).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transactions import process' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('Unable to get transactions import process', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transactions import process' });
} else {
reject(error);
}
});
});
}
function uploadTransactionPicture({ pictureFile, clientSessionId }: { pictureFile: File, clientSessionId?: string }): Promise<TransactionPictureInfoBasicResponse> {
return new Promise((resolve, reject) => {
services.uploadTransactionPicture({ pictureFile, clientSessionId }).then(response => {
@@ -1243,6 +1268,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
parseImportDsvFile,
parseImportTransaction,
importTransactions,
getImportTransactionsProcess,
uploadTransactionPicture,
removeUnusedTransactionPicture,
getTransactionPictureUrl,
@@ -786,7 +786,7 @@
<v-btn color="teal" :disabled="submitting || !!editingTransaction || selectedImportTransactionCount < 1 || selectedInvalidTransactionCount > 0"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="submit"
v-if="currentStep === 'checkData'">
{{ tt('Import') }}
{{ (submitting && importProcess > 0 ? tt('format.misc.importingTransactions', { process: importProcess.toFixed(2) }) : tt('Import')) }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="submitting"></v-progress-circular>
</v-btn>
<v-btn color="secondary" variant="tonal"
@@ -969,6 +969,7 @@ const fileInput = useTemplateRef<HTMLInputElement>('fileInput');
const showState = ref<boolean>(false);
const clientSessionId = ref<string>('');
const currentStep = ref<ImportTransactionDialogStep>('uploadFile');
const importProcess = ref<number>(0);
const fileType = ref<string>('ezbookkeeping');
const fileSubType = ref<string>('ezbookkeeping_csv');
const fileEncoding = ref<string>('utf-8');
@@ -1927,6 +1928,7 @@ function open(): Promise<void> {
fileSubType.value = 'ezbookkeeping_csv';
fileEncoding.value = 'utf-8';
currentStep.value = 'uploadFile';
importProcess.value = 0;
importFile.value = null;
importData.value = '';
parsedFileData.value = undefined;
@@ -2221,10 +2223,48 @@ function submit(): void {
editingTags.value = [];
submitting.value = true;
let showProcessTimer : number | undefined = undefined;
if (transactions.length > 100) {
setTimeout(() => {
if (!submitting.value) {
logger.warn('transaction import is not submitting');
return;
}
// @ts-expect-error the return value of setInterval is number, but lint shows it as NodeJS.Timer
showProcessTimer = setInterval(() => {
if (submitting.value) {
transactionsStore.getImportTransactionsProcess({
clientSessionId: clientSessionId.value
}).then(response => {
if (isNumber(response) && 0 <= response && response < 100) {
importProcess.value = response;
} else {
importProcess.value = 0;
clearInterval(showProcessTimer);
showProcessTimer = undefined;
}
}).catch(() => {
importProcess.value = 0;
clearInterval(showProcessTimer);
showProcessTimer = undefined;
});
}
}, 2000);
}, 2000);
}
transactionsStore.importTransactions({
transactions: transactions,
clientSessionId: clientSessionId.value
}).then(response => {
if (showProcessTimer) {
importProcess.value = 0;
clearInterval(showProcessTimer);
showProcessTimer = undefined;
}
importedCount.value = response;
currentStep.value = 'finalResult';
@@ -2235,6 +2275,12 @@ function submit(): void {
submitting.value = false;
}).catch(error => {
if (showProcessTimer) {
importProcess.value = 0;
clearInterval(showProcessTimer);
showProcessTimer = undefined;
}
submitting.value = false;
if (!error.processed) {