support batch replace category / account / tag in import transaction dialog

This commit is contained in:
MaysWind
2024-09-23 23:47:02 +08:00
parent 29781bbac4
commit d32cd793d0
4 changed files with 167 additions and 31 deletions
+5 -1
View File
@@ -1509,7 +1509,11 @@
"No data to import": "No data to import",
"Cannot import invalid transactions": "Cannot import invalid transactions",
"Unable to parse import file": "Unable to parse import file",
"Batch Replace": "Batch Replace",
"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",
"Batch Replace Selected Accounts": "Batch Replace Selected Accounts",
"Batch Replace Selected Destination Accounts": "Batch Replace Selected Destination Accounts",
"Replace Invalid Expense Categories": "Replace Invalid Expense Categories",
"Replace Invalid Income Categories": "Replace Invalid Income Categories",
"Replace Invalid Transfer Categories": "Replace Invalid Transfer Categories",
+5 -1
View File
@@ -1509,7 +1509,11 @@
"No data to import": "没有可以导入的数据",
"Cannot import invalid transactions": "不能导入无效的交易",
"Unable to parse import file": "无法解析导入的文件",
"Batch Replace": "批量替换",
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
"Batch Replace Selected Income Categories": "批量替换选中的收入分类",
"Batch Replace Selected Transfer Categories": "批量替换选中的转账分类",
"Batch Replace Selected Accounts": "批量替换选中的账户",
"Batch Replace Selected Destination Accounts": "批量替换选中的目标账户",
"Replace Invalid Expense Categories": "替换无效的支出分类",
"Replace Invalid Income Categories": "替换无效的收入分类",
"Replace Invalid Transfer Categories": "替换无效的转账分类",
@@ -3,16 +3,21 @@
<v-card class="pa-2 pa-sm-4 pa-md-4">
<template #title>
<div class="d-flex align-center justify-center">
<h4 class="text-h4" v-if="type === 'expenseCategory'">{{ $t('Replace Invalid Expense Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'incomeCategory'">{{ $t('Replace Invalid Income Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'transferCategory'">{{ $t('Replace Invalid Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'account'">{{ $t('Replace Invalid Accounts') }}</h4>
<h4 class="text-h4" v-if="type === 'tag'">{{ $t('Replace Invalid Transaction Tags') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'expenseCategory'">{{ $t('Batch Replace Selected Expense Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'incomeCategory'">{{ $t('Batch Replace Selected Income Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'transferCategory'">{{ $t('Batch Replace Selected Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'account'">{{ $t('Batch Replace Selected Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'destinationAccount'">{{ $t('Batch Replace Selected Destination Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'expenseCategory'">{{ $t('Replace Invalid Expense Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'incomeCategory'">{{ $t('Replace Invalid Income Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'transferCategory'">{{ $t('Replace Invalid Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'account'">{{ $t('Replace Invalid Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'tag'">{{ $t('Replace Invalid Transaction Tags') }}</h4>
</div>
</template>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory'">
<v-row>
<v-col cols="12">
<v-col cols="12" v-if="mode === 'replaceInvalidItems'">
<v-autocomplete
item-title="name"
item-value="value"
@@ -76,9 +81,9 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'account'">
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'account' || type === 'destinationAccount'">
<v-row>
<v-col cols="12">
<v-col cols="12" v-if="mode === 'replaceInvalidItems'">
<v-autocomplete
item-title="name"
item-value="value"
@@ -111,7 +116,7 @@
</v-card-text>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'tag'">
<v-row>
<v-col cols="12">
<v-col cols="12" v-if="mode === 'replaceInvalidItems'">
<v-autocomplete
item-title="name"
item-value="value"
@@ -157,7 +162,7 @@
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-btn :disabled="(!sourceItem && sourceItem !== '') || (!targetItem && targetItem !== '')" @click="confirm">{{ $t('OK') }}</v-btn>
<v-btn :disabled="(mode === 'replaceInvalidItems' && !sourceItem && sourceItem !== '') || (!targetItem && targetItem !== '')" @click="confirm">{{ $t('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" @click="cancel">{{ $t('Cancel') }}</v-btn>
</div>
</v-card-text>
@@ -198,6 +203,7 @@ export default {
data() {
return {
showState: false,
mode: '',
type: '',
invalidItems: [],
sourceItem: null,
@@ -264,9 +270,16 @@ export default {
methods: {
open(options) {
const self = this;
self.mode = options.mode;
self.type = options.type;
self.invalidItems = options.invalidItems;
self.sourceItem = null;
if (self.mode === 'batchReplace') {
self.invalidItems = null;
} else if (self.mode === 'replaceInvalidItems') {
self.invalidItems = options.invalidItems;
}
self.targetItem = null;
self.showState = true;
@@ -277,10 +290,16 @@ export default {
},
confirm() {
if (this.resolve) {
this.resolve({
sourceItem: this.sourceItem,
targetItem: this.targetItem
});
if (this.mode === 'batchReplace') {
this.resolve({
targetItem: this.targetItem
});
} else if (this.mode === 'replaceInvalidItems') {
this.resolve({
sourceItem: this.sourceItem,
targetItem: this.targetItem
});
}
}
this.showState = false;
@@ -12,6 +12,27 @@
<v-icon :icon="icons.more" />
<v-menu activator="parent">
<v-list>
<v-list-item :prepend-icon="icons.replace"
:disabled="selectedExpenseTransactionCount < 1"
:title="$t('Batch Replace Selected Expense Categories')"
@click="showBatchReplaceDialog('expenseCategory')"></v-list-item>
<v-list-item :prepend-icon="icons.replace"
:disabled="selectedIncomeTransactionCount < 1"
:title="$t('Batch Replace Selected Income Categories')"
@click="showBatchReplaceDialog('incomeCategory')"></v-list-item>
<v-list-item :prepend-icon="icons.replace"
:disabled="selectedTransferTransactionCount < 1"
:title="$t('Batch Replace Selected Transfer Categories')"
@click="showBatchReplaceDialog('transferCategory')"></v-list-item>
<v-list-item :prepend-icon="icons.replace"
:disabled="selectedImportTransactionCount < 1"
:title="$t('Batch Replace Selected Accounts')"
@click="showBatchReplaceDialog('account')"></v-list-item>
<v-list-item :prepend-icon="icons.replace"
:disabled="selectedTransferTransactionCount < 1"
:title="$t('Batch Replace Selected Destination Accounts')"
@click="showBatchReplaceDialog('destinationAccount')"></v-list-item>
<v-divider class="my-2"/>
<v-list-item :prepend-icon="icons.replace"
:disabled="allInvalidExpenseCategoryNames < 1"
:title="$t('Replace Invalid Expense Categories')"
@@ -241,7 +262,7 @@
<v-icon class="mr-1" :icon="icons.alert"/>
<span>{{ item.originalSourceAccountName }}</span>
</div>
<v-icon class="mx-1" size="13" :icon="icons.arrowRight" v-if="item.type === allTransactionTypes.Transfer && item.sourceAccountId !== item.destinationAccountId"></v-icon>
<v-icon class="mx-1" size="13" :icon="icons.arrowRight" v-if="item.type === allTransactionTypes.Transfer"></v-icon>
<span v-if="item.type === allTransactionTypes.Transfer && item.destinationAccountId && item.destinationAccountId !== '0' && allAccountsMap[item.destinationAccountId]">{{allAccountsMap[item.destinationAccountId].name }}</span>
<div class="text-error font-italic" v-else-if="item.type === allTransactionTypes.Transfer && (!item.destinationAccountId || item.destinationAccountId === '0' || !allAccountsMap[item.destinationAccountId])">
<v-icon class="mr-1" :icon="icons.alert"/>
@@ -431,14 +452,14 @@
</v-card>
</v-dialog>
<replace-invalid-item-dialog ref="replaceInvalidItemDialog" />
<batch-replace-dialog ref="batchReplaceDialog" />
<confirm-dialog ref="confirmDialog"/>
<snack-bar ref="snackbar" />
<input ref="fileInput" type="file" style="display: none" :accept="supportedImportFileExtensions" @change="setImportFile($event)" />
</template>
<script>
import ReplaceInvalidItemDialog from './ReplaceInvalidItemDialog.vue';
import BatchReplaceDialog from './BatchReplaceDialog.vue';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/setting.js';
@@ -484,7 +505,7 @@ import {
export default {
components: {
ReplaceInvalidItemDialog
BatchReplaceDialog
},
props: [
'persistent'
@@ -707,6 +728,39 @@ export default {
return count;
},
selectedExpenseTransactionCount() {
let count = 0;
for (let i = 0; i < this.importTransactions.length; i++) {
if (this.importTransactions[i].selected && this.importTransactions[i].type === this.allTransactionTypes.Expense) {
count++;
}
}
return count;
},
selectedIncomeTransactionCount() {
let count = 0;
for (let i = 0; i < this.importTransactions.length; i++) {
if (this.importTransactions[i].selected && this.importTransactions[i].type === this.allTransactionTypes.Income) {
count++;
}
}
return count;
},
selectedTransferTransactionCount() {
let count = 0;
for (let i = 0; i < this.importTransactions.length; i++) {
if (this.importTransactions[i].selected && this.importTransactions[i].type === this.allTransactionTypes.Transfer) {
count++;
}
}
return count;
},
selectedInvalidTransactionCount() {
let count = 0;
@@ -938,23 +992,17 @@ export default {
},
selectAllInThisPage() {
for (let i = Math.max(0, (this.currentPage - 1) * this.countPerPage); i < Math.min(this.importTransactions.length, this.currentPage * this.countPerPage); i++) {
if (this.importTransactions[i] && this.importTransactions[i].valid) {
this.importTransactions[i].selected = true;
}
this.importTransactions[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++) {
if (this.importTransactions[i]) {
this.importTransactions[i].selected = false;
}
this.importTransactions[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++) {
if (this.importTransactions[i] && (this.importTransactions[i].valid || this.importTransactions[i].selected)) {
this.importTransactions[i].selected = !this.importTransactions[i].selected;
}
this.importTransactions[i].selected = !this.importTransactions[i].selected;
}
},
editTransaction(transaction) {
@@ -967,10 +1015,71 @@ export default {
updateTransactionData(transaction) {
transaction.valid = this.isTransactionValid(transaction);
},
showBatchReplaceDialog(type) {
const self = this;
self.$refs.batchReplaceDialog.open({
mode: 'batchReplace',
type: type
}).then(result => {
if (!result || !result.targetItem) {
return;
}
let updatedCount = 0;
for (let i = 0; i < self.importTransactions.length; i++) {
const transaction = self.importTransactions[i];
if (!transaction.selected) {
continue;
}
let updated = false;
if (type === 'expenseCategory') {
if (transaction.type === self.allTransactionTypes.Expense) {
transaction.categoryId = result.targetItem;
updated = true;
}
} else if (type === 'incomeCategory') {
if (transaction.type === self.allTransactionTypes.Income) {
transaction.categoryId = result.targetItem;
updated = true;
}
} else if (type === 'transferCategory') {
if (transaction.type === self.allTransactionTypes.Transfer) {
transaction.categoryId = result.targetItem;
updated = true;
}
} else if (type === 'account') {
transaction.sourceAccountId = result.targetItem;
updated = true;
} else if (type === 'destinationAccount') {
if (transaction.type === self.allTransactionTypes.Transfer) {
transaction.destinationAccountId = result.targetItem;
updated = true;
}
}
if (updated) {
updatedCount++;
self.updateTransactionData(transaction);
}
}
if (updatedCount > 0) {
self.$refs.snackbar.showMessage('format.misc.youHaveUpdatedTransactions', {
count: updatedCount
});
}
});
},
showReplaceInvalidItemDialog(type, invalidItems) {
const self = this;
self.$refs.replaceInvalidItemDialog.open({
self.$refs.batchReplaceDialog.open({
mode: 'replaceInvalidItems',
type: type,
invalidItems: invalidItems
}).then(result => {