diff --git a/src/locales/en.json b/src/locales/en.json
index 4a1b0724..fa94557f 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1526,6 +1526,7 @@
"Check and Modify Your Data": "Check and Modify Your Data",
"Data Import Completed": "Data Import Completed",
"File Type": "File Type",
+ "Filter Description": "Filter Description",
"How to export this file?": "How to export this file?",
"ezbookkeeping Data Export File": "ezbookkeeping Data Export File",
"Open Financial Exchange (OFX) File": "Open Financial Exchange (OFX) File",
diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json
index 3672651d..dd158d57 100644
--- a/src/locales/zh_Hans.json
+++ b/src/locales/zh_Hans.json
@@ -1526,6 +1526,7 @@
"Check and Modify Your Data": "检查及修改您的数据",
"Data Import Completed": "数据导入完成",
"File Type": "文件类型",
+ "Filter Description": "过滤描述",
"How to export this file?": "如何导出该文件?",
"ezbookkeeping Data Export File": "ezbookkeeping 数据导出文件",
"Open Financial Exchange (OFX) File": "开放式金融交换 (OFX) 文件",
diff --git a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue
index 5d6b4e85..022c8df9 100644
--- a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue
+++ b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue
@@ -7,6 +7,93 @@
{{ $t('Import Transactions') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -125,6 +212,8 @@
:height="importTransactionsTableHeight"
:headers="importTransactionHeaders"
:items="importTransactions"
+ :search="JSON.stringify(filters)"
+ :custom-filter="importTransactionsFilter"
:no-data-text="$t('No data to import')"
v-model:items-per-page="countPerPage"
v-model:page="currentPage"
@@ -467,6 +556,36 @@
+
+
+
+
+
{{ $t('Filter Description') }}
+
+
+
+
+
+
+
+ {{ $t('OK') }}
+ {{ $t('Cancel') }}
+
+
+
+
+
+
@@ -489,7 +608,12 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import categoryConstants from '@/consts/category.js';
import transactionConstants from '@/consts/transaction.js';
-import { getNameByKeyValue } from '@/lib/common.js';
+import {
+ isString,
+ isNumber,
+ getNameByKeyValue,
+ objectFieldToArrayItem
+} from '@/lib/common.js';
import { isFileExtensionSupported } from '@/lib/file.js';
import { generateRandomUUID } from '@/lib/misc.js';
import logger from '@/lib/logger.js';
@@ -506,12 +630,13 @@ import {
} from '@/lib/category.js';
import {
+ mdiFilterOutline,
+ mdiCheck,
mdiDotsVertical,
mdiHelpCircleOutline,
mdiFindReplace,
mdiClose,
mdiArrowRight,
- mdiCheck,
mdiSelectAll,
mdiSelect,
mdiSelectInverse,
@@ -541,14 +666,28 @@ export default {
importTransactions: null,
editingTransaction: null,
editingTags: [],
+ filters: {
+ minDatetime: null,
+ maxDatetime: null,
+ transactionType: null,
+ category: null,
+ account: null,
+ tag: null,
+ description: null
+ },
currentPage: 1,
countPerPage: 10,
importedCount: null,
+ showCustomDateRangeDialog: false,
+ showCustomDescriptionDialog: false,
+ currentDescriptionFilterValue: null,
loading: true,
submitting: false,
resolve: null,
reject: null,
icons: {
+ filter: mdiFilterOutline,
+ checked: mdiCheck,
more: mdiDotsVertical,
document: mdiHelpCircleOutline,
replace: mdiFindReplace,
@@ -766,7 +905,36 @@ export default {
return 1;
}
- return Math.ceil(this.importTransactions.length / this.countPerPage);
+ let count = 0;
+
+ for (let i = 0; i < this.importTransactions.length; i++) {
+ if (this.isTransactionDisplayed(this.importTransactions[i])) {
+ count++;
+ }
+ }
+
+ return Math.ceil(count / this.countPerPage);
+ },
+ currentPageTransactions() {
+ const ret = [];
+ const previousCount = Math.max(0, (this.currentPage - 1) * this.countPerPage);
+ let count = 0;
+
+ for (let i = 0; i < this.importTransactions.length; i++) {
+ if (ret.length >= this.countPerPage) {
+ break;
+ }
+
+ if (this.isTransactionDisplayed(this.importTransactions[i])) {
+ if (count >= previousCount) {
+ ret.push(this.importTransactions[i]);
+ }
+
+ count++;
+ }
+ }
+
+ return ret;
},
selectedImportTransactionCount() {
let count = 0;
@@ -833,6 +1001,15 @@ export default {
return this.selectedImportTransactionCount === this.importTransactions.length;
}
},
+ allUsedCategoryNames() {
+ return this.getAllUsedCategoryNames();
+ },
+ allUsedAccountNames() {
+ return this.getAllUsedAccountNames();
+ },
+ allUsedTagNames() {
+ return this.getAllUsedTagNames();
+ },
allInvalidExpenseCategoryNames() {
return this.getCurrentInvalidCategoryNames(this.allTransactionTypes.Expense);
},
@@ -847,6 +1024,16 @@ export default {
},
allInvalidTransactionTagNames() {
return this.getCurrentInvalidTagNames();
+ },
+ displayFilterCustomDateRange() {
+ if (this.filters.minDatetime === null || this.filters.maxDatetime === null) {
+ return '';
+ }
+
+ const minDisplayTime = this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.filters.minDatetime);
+ const maxDisplayTime = this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.filters.maxDatetime);
+
+ return `${minDisplayTime} - ${maxDisplayTime}`
}
},
watch: {
@@ -884,6 +1071,13 @@ export default {
self.importTransactions = null;
self.editingTransaction = null;
self.editingTags = [];
+ self.filters.minDatetime = null;
+ self.filters.maxDatetime = null;
+ self.filters.transactionType = null;
+ self.filters.category = null;
+ self.filters.account = null;
+ self.filters.tag = null;
+ self.filters.description = null;
self.currentPage = 1;
self.countPerPage = 10;
self.showState = true;
@@ -954,6 +1148,7 @@ export default {
transaction.valid = self.isTransactionValid(transaction);
transaction.actualCategoryName = transaction.originalCategoryName;
transaction.actualSourceAccountName = transaction.originalSourceAccountName;
+ transaction.actualDestinationAccountName = transaction.originalDestinationAccountName;
}
}
@@ -1045,48 +1240,156 @@ export default {
this.showState = false;
},
+ changeCustomDateFilter(minTime, maxTime) {
+ this.filters.minDatetime = minTime;
+ this.filters.maxDatetime = maxTime;
+ this.showCustomDateRangeDialog = false;
+ },
+ importTransactionsFilter(value, query, item) {
+ if (!item || !item.raw) {
+ return false;
+ }
+
+ return this.isTransactionDisplayed(item.raw);
+ },
+ isTransactionDisplayed(transaction) {
+ if (isNumber(this.filters.minDatetime) && isNumber(this.filters.maxDatetime) && (transaction.time < this.filters.minDatetime || transaction.time > this.filters.maxDatetime)) {
+ return false;
+ }
+
+ if (isNumber(this.filters.transactionType) && transaction.type !== this.filters.transactionType) {
+ return false;
+ }
+
+ if (isString(this.filters.category)) {
+ if (this.filters.category === '' && transaction.actualCategoryName !== '') {
+ return false;
+ } else if (this.filters.category !== '' && transaction.actualCategoryName !== this.filters.category) {
+ return false;
+ }
+ } else if (this.filters.category === undefined) {
+ if (transaction.type !== this.allTransactionTypes.ModifyBalance && transaction.categoryId && transaction.categoryId !== '0') {
+ return false;
+ }
+ }
+
+ if (isString(this.filters.account)) {
+ if (this.filters.account === '' && (transaction.actualSourceAccountName !== '' || transaction.actualDestinationAccountName !== '')) {
+ return false;
+ } else if (this.filters.account !== '' && transaction.actualSourceAccountName !== this.filters.account && transaction.actualDestinationAccountName !== this.filters.account) {
+ return false;
+ }
+ } else if (this.filters.account === undefined) {
+ if (transaction.type !== this.allTransactionTypes.Transfer && transaction.sourceAccountId && transaction.sourceAccountId !== '0') {
+ return false;
+ } else if (transaction.type === this.allTransactionTypes.Transfer && transaction.sourceAccountId && transaction.sourceAccountId !== '0' && transaction.destinationAccountId && transaction.destinationAccountId !== '0') {
+ return false;
+ }
+ }
+
+ if (isString(this.filters.tag)) {
+ if (this.filters.tag === '' && transaction.tagIds && transaction.tagIds.length) {
+ return false;
+ } else if (this.filters.tag !== '') {
+ let hasTagName = false;
+
+ if (transaction.tagIds && transaction.tagIds.length) {
+ for (let i = 0; i < transaction.tagIds.length; i++) {
+ const tagId = transaction.tagIds[i];
+ let tagName = transaction.originalTagNames ? transaction.originalTagNames[i] : "";
+
+ if (tagId && tagId !== '0' && this.allTagsMap[tagId] && this.allTagsMap[tagId].name) {
+ tagName = this.allTagsMap[tagId].name;
+ }
+
+ if (tagName === this.filters.tag) {
+ hasTagName = true;
+ break;
+ }
+ }
+ }
+
+ if (!hasTagName) {
+ return false;
+ }
+ }
+ } else if (this.filters.tag === undefined) {
+ if (transaction.tagIds && transaction.tagIds.length) {
+ let hasInvalidTag = false;
+
+ for (let i = 0; i < transaction.tagIds.length; i++) {
+ if (!transaction.tagIds[i] || transaction.tagIds[i] === '0') {
+ hasInvalidTag = true;
+ break;
+ }
+ }
+
+ if (!hasInvalidTag) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (isString(this.filters.description)) {
+ if (this.filters.description === '' && transaction.comment !== '') {
+ return false;
+ } else if (this.filters.description !== '' && transaction.comment.indexOf(this.filters.description) < 0) {
+ return false;
+ }
+ }
+
+ return true;
+ },
selectAllValid() {
for (let i = 0; i < this.importTransactions.length; i++) {
- if (this.importTransactions[i].valid) {
+ if (this.importTransactions[i].valid && this.isTransactionDisplayed(this.importTransactions[i])) {
this.importTransactions[i].selected = true;
}
}
},
selectAllInvalid() {
for (let i = 0; i < this.importTransactions.length; i++) {
- if (!this.importTransactions[i].valid) {
+ if (!this.importTransactions[i].valid && this.isTransactionDisplayed(this.importTransactions[i])) {
this.importTransactions[i].selected = true;
}
}
},
selectAll() {
for (let i = 0; i < this.importTransactions.length; i++) {
- this.importTransactions[i].selected = true;
+ if (this.isTransactionDisplayed(this.importTransactions[i])) {
+ this.importTransactions[i].selected = true;
+ }
}
},
selectNone() {
for (let i = 0; i < this.importTransactions.length; i++) {
- this.importTransactions[i].selected = false;
+ if (this.isTransactionDisplayed(this.importTransactions[i])) {
+ this.importTransactions[i].selected = false;
+ }
}
},
selectInvert() {
for (let i = 0; i < this.importTransactions.length; i++) {
- this.importTransactions[i].selected = !this.importTransactions[i].selected;
+ if (this.isTransactionDisplayed(this.importTransactions[i])) {
+ this.importTransactions[i].selected = !this.importTransactions[i].selected;
+ }
}
},
selectAllInThisPage() {
- for (let i = Math.max(0, (this.currentPage - 1) * this.countPerPage); i < Math.min(this.importTransactions.length, this.currentPage * this.countPerPage); i++) {
- this.importTransactions[i].selected = true;
+ for (let i = 0; i < this.currentPageTransactions.length; i++) {
+ this.currentPageTransactions[i].selected = true;
}
},
selectNoneInThisPage() {
- for (let i = Math.max(0, (this.currentPage - 1) * this.countPerPage); i < Math.min(this.importTransactions.length, this.currentPage * this.countPerPage); i++) {
- this.importTransactions[i].selected = false;
+ for (let i = 0; i < this.currentPageTransactions.length; i++) {
+ this.currentPageTransactions[i].selected = false;
}
},
selectInvertInThisPage() {
- for (let i = Math.max(0, (this.currentPage - 1) * this.countPerPage); i < Math.min(this.importTransactions.length, this.currentPage * this.countPerPage); i++) {
- this.importTransactions[i].selected = !this.importTransactions[i].selected;
+ for (let i = 0; i < this.currentPageTransactions.length; i++) {
+ this.currentPageTransactions[i].selected = !this.currentPageTransactions[i].selected;
}
},
editTransaction(transaction) {
@@ -1113,6 +1416,10 @@ export default {
if (transaction.sourceAccountId && this.allAccountsMap[transaction.sourceAccountId]) {
transaction.actualSourceAccountName = this.allAccountsMap[transaction.sourceAccountId].name;
}
+
+ if (transaction.destinationAccountId && this.allAccountsMap[transaction.destinationAccountId]) {
+ transaction.actualDestinationAccountName = this.allAccountsMap[transaction.destinationAccountId].name;
+ }
},
showBatchReplaceDialog(type) {
const self = this;
@@ -1261,6 +1568,60 @@ export default {
}
});
},
+ getAllUsedCategoryNames() {
+ const categoryNames = {};
+
+ for (let i = 0; i < this.importTransactions.length; i++) {
+ const transaction = this.importTransactions[i];
+
+ if (transaction.actualCategoryName && transaction.actualCategoryName !== '') {
+ categoryNames[transaction.actualCategoryName] = true;
+ }
+ }
+
+ return objectFieldToArrayItem(categoryNames);
+ },
+ getAllUsedAccountNames() {
+ const accountNames = {};
+
+ for (let i = 0; i < this.importTransactions.length; i++) {
+ const transaction = this.importTransactions[i];
+
+ if (transaction.actualSourceAccountName && transaction.actualSourceAccountName !== '') {
+ accountNames[transaction.actualSourceAccountName] = true;
+ }
+
+ if (transaction.actualDestinationAccountName && transaction.actualDestinationAccountName !== '') {
+ accountNames[transaction.actualDestinationAccountName] = true;
+ }
+ }
+
+ return objectFieldToArrayItem(accountNames);
+ },
+ getAllUsedTagNames(){
+ const tagNames = {};
+
+ for (let i = 0; i < this.importTransactions.length; i++) {
+ const transaction = this.importTransactions[i];
+
+ if (!transaction.tagIds || !transaction.originalTagNames) {
+ continue;
+ }
+
+ for (let j = 0; j < transaction.tagIds.length; j++) {
+ const tagId = transaction.tagIds[j];
+ const originalTagName = transaction.originalTagNames[j];
+
+ if (tagId && tagId !== '0' && this.allTagsMap[tagId] && this.allTagsMap[tagId].name) {
+ tagNames[this.allTagsMap[tagId].name] = true;
+ } else if (originalTagName) {
+ tagNames[originalTagName] = true;
+ }
+ }
+ }
+
+ return objectFieldToArrayItem(tagNames);
+ },
getCurrentInvalidCategoryNames(transactionType) {
const invalidCategoryNames = {};
const invalidCategories = [];