mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 07:57:33 +08:00
support batch replace category / account / tag in import transaction dialog
This commit is contained in:
@@ -37,9 +37,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showMessage(message) {
|
showMessage(message, options) {
|
||||||
this.showState = true;
|
this.showState = true;
|
||||||
this.messageContent = this.$t(message);
|
this.messageContent = this.$t(message, options);
|
||||||
},
|
},
|
||||||
showError(error) {
|
showError(error) {
|
||||||
this.showState = true;
|
this.showState = true;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
"youHaveAccounts": "You have recorded {count} accounts",
|
"youHaveAccounts": "You have recorded {count} accounts",
|
||||||
"clickToSelectedFile": "Click to select import file ({extensions})",
|
"clickToSelectedFile": "Click to select import file ({extensions})",
|
||||||
"selectedCount": "Selected {count} of {totalCount}",
|
"selectedCount": "Selected {count} of {totalCount}",
|
||||||
|
"youHaveUpdatedTransactions": "You have updated {count} transactions",
|
||||||
"confirmImportTransactions": "Are you sure you want to import {count} transactions?",
|
"confirmImportTransactions": "Are you sure you want to import {count} transactions?",
|
||||||
"importTransactionResult": "You have imported {count} transactions successfully.",
|
"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.",
|
"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.",
|
||||||
@@ -1508,6 +1509,19 @@
|
|||||||
"No data to import": "No data to import",
|
"No data to import": "No data to import",
|
||||||
"Cannot import invalid transactions": "Cannot import invalid transactions",
|
"Cannot import invalid transactions": "Cannot import invalid transactions",
|
||||||
"Unable to parse import file": "Unable to parse import file",
|
"Unable to parse import file": "Unable to parse import file",
|
||||||
|
"Batch Replace": "Batch Replace",
|
||||||
|
"Replace Invalid Expense Categories": "Replace Invalid Expense Categories",
|
||||||
|
"Replace Invalid Income Categories": "Replace Invalid Income Categories",
|
||||||
|
"Replace Invalid Transfer Categories": "Replace Invalid Transfer Categories",
|
||||||
|
"Replace Invalid Accounts": "Replace Invalid Accounts",
|
||||||
|
"Replace Invalid Transaction Tags": "Replace Invalid Transaction Tags",
|
||||||
|
"Invalid Category": "Invalid Category",
|
||||||
|
"Target Category": "Target Category",
|
||||||
|
"Invalid Account": "Invalid Account",
|
||||||
|
"Target Account": "Target Account",
|
||||||
|
"Invalid Tag": "Invalid Tag",
|
||||||
|
"Target Tag": "Target Tag",
|
||||||
|
"(Empty)": "(Empty)",
|
||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Your transaction description (optional)": "Your transaction description (optional)",
|
"Your transaction description (optional)": "Your transaction description (optional)",
|
||||||
"Transaction category cannot be blank": "Transaction category cannot be blank",
|
"Transaction category cannot be blank": "Transaction category cannot be blank",
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
"youHaveAccounts": "您已经记录了 {count} 个账户",
|
"youHaveAccounts": "您已经记录了 {count} 个账户",
|
||||||
"clickToSelectedFile": "点击选择导入文件 ({extensions})",
|
"clickToSelectedFile": "点击选择导入文件 ({extensions})",
|
||||||
"selectedCount": "已选择 {count} / {totalCount}",
|
"selectedCount": "已选择 {count} / {totalCount}",
|
||||||
|
"youHaveUpdatedTransactions": "您已经更新 {count} 个交易",
|
||||||
"confirmImportTransactions": "您确定要导入 {count} 个交易?",
|
"confirmImportTransactions": "您确定要导入 {count} 个交易?",
|
||||||
"importTransactionResult": "您已经成功导入 {count} 个交易。",
|
"importTransactionResult": "您已经成功导入 {count} 个交易。",
|
||||||
"accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。",
|
"accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。",
|
||||||
@@ -1508,6 +1509,19 @@
|
|||||||
"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": "批量替换",
|
||||||
|
"Replace Invalid Expense Categories": "替换无效的支出分类",
|
||||||
|
"Replace Invalid Income Categories": "替换无效的收入分类",
|
||||||
|
"Replace Invalid Transfer Categories": "替换无效的转账分类",
|
||||||
|
"Replace Invalid Accounts": "替换无效的账户",
|
||||||
|
"Replace Invalid Transaction Tags": "替换无效的交易标签",
|
||||||
|
"Invalid Category": "无效分类",
|
||||||
|
"Target Category": "目标分类",
|
||||||
|
"Invalid Account": "无效账户",
|
||||||
|
"Target Account": "目标账户",
|
||||||
|
"Invalid Tag": "无效标签",
|
||||||
|
"Target Tag": "目标标签",
|
||||||
|
"(Empty)": "(空白)",
|
||||||
"Tags": "标签",
|
"Tags": "标签",
|
||||||
"Your transaction description (optional)": "你的交易描述 (可选)",
|
"Your transaction description (optional)": "你的交易描述 (可选)",
|
||||||
"Transaction category cannot be blank": "交易分类不能为空",
|
"Transaction category cannot be blank": "交易分类不能为空",
|
||||||
|
|||||||
@@ -7,6 +7,34 @@
|
|||||||
<h4 class="text-h4">{{ $t('Import Transactions') }}</h4>
|
<h4 class="text-h4">{{ $t('Import Transactions') }}</h4>
|
||||||
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
||||||
</div>
|
</div>
|
||||||
|
<v-btn density="comfortable" color="default" variant="text" class="ml-2" :icon="true"
|
||||||
|
v-if="currentStep === 'checkData'">
|
||||||
|
<v-icon :icon="icons.more" />
|
||||||
|
<v-menu activator="parent">
|
||||||
|
<v-list>
|
||||||
|
<v-list-item :prepend-icon="icons.replace"
|
||||||
|
:disabled="allInvalidExpenseCategoryNames < 1"
|
||||||
|
:title="$t('Replace Invalid Expense Categories')"
|
||||||
|
@click="showReplaceInvalidItemDialog('expenseCategory', allInvalidExpenseCategoryNames)"></v-list-item>
|
||||||
|
<v-list-item :prepend-icon="icons.replace"
|
||||||
|
:disabled="allInvalidIncomeCategoryNames < 1"
|
||||||
|
:title="$t('Replace Invalid Income Categories')"
|
||||||
|
@click="showReplaceInvalidItemDialog('incomeCategory', allInvalidIncomeCategoryNames)"></v-list-item>
|
||||||
|
<v-list-item :prepend-icon="icons.replace"
|
||||||
|
:disabled="allInvalidTransferCategoryNames < 1"
|
||||||
|
:title="$t('Replace Invalid Transfer Categories')"
|
||||||
|
@click="showReplaceInvalidItemDialog('transferCategory', allInvalidTransferCategoryNames)"></v-list-item>
|
||||||
|
<v-list-item :prepend-icon="icons.replace"
|
||||||
|
:disabled="allInvalidAccountNames < 1"
|
||||||
|
:title="$t('Replace Invalid Accounts')"
|
||||||
|
@click="showReplaceInvalidItemDialog('account', allInvalidAccountNames)"></v-list-item>
|
||||||
|
<v-list-item :prepend-icon="icons.replace"
|
||||||
|
:disabled="allInvalidTransactionTagNames < 1"
|
||||||
|
:title="$t('Replace Invalid Transaction Tags')"
|
||||||
|
@click="showReplaceInvalidItemDialog('tag', allInvalidTransactionTagNames)"></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -131,7 +159,7 @@
|
|||||||
<v-chip label color="primary" variant="outlined" size="x-small" v-else-if="value === allTransactionTypes.Transfer">{{ $t('Transfer') }}</v-chip>
|
<v-chip label color="primary" variant="outlined" size="x-small" v-else-if="value === allTransactionTypes.Transfer">{{ $t('Transfer') }}</v-chip>
|
||||||
<v-chip label color="default" variant="outlined" size="x-small" v-else>{{ $t('Unknown') }}</v-chip>
|
<v-chip label color="default" variant="outlined" size="x-small" v-else>{{ $t('Unknown') }}</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template #item.categoryId="{ item }">
|
<template #item.originalCategoryName="{ item }">
|
||||||
<div class="d-flex align-center" v-if="editingTransaction !== item || item.type === allTransactionTypes.ModifyBalance">
|
<div class="d-flex align-center" v-if="editingTransaction !== item || item.type === allTransactionTypes.ModifyBalance">
|
||||||
<span v-if="item.type === allTransactionTypes.ModifyBalance">-</span>
|
<span v-if="item.type === allTransactionTypes.ModifyBalance">-</span>
|
||||||
<ItemIcon size="24px" icon-type="category"
|
<ItemIcon size="24px" icon-type="category"
|
||||||
@@ -206,7 +234,7 @@
|
|||||||
<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 && item.sourceAccountId !== item.destinationAccountId"></v-icon>
|
||||||
<span v-if="item.type === allTransactionTypes.Transfer && item.sourceAccountId !== item.destinationAccountId">{{ getTransactionDisplayDestinationAmount(item) }}</span>
|
<span v-if="item.type === allTransactionTypes.Transfer && item.sourceAccountId !== item.destinationAccountId">{{ getTransactionDisplayDestinationAmount(item) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #item.sourceAccountId="{ item }">
|
<template #item.originalSourceAccountName="{ item }">
|
||||||
<div class="d-flex align-center" v-if="editingTransaction !== item">
|
<div class="d-flex align-center" v-if="editingTransaction !== item">
|
||||||
<span v-if="item.sourceAccountId && item.sourceAccountId !== '0' && allAccountsMap[item.sourceAccountId]">{{ allAccountsMap[item.sourceAccountId].name }}</span>
|
<span v-if="item.sourceAccountId && item.sourceAccountId !== '0' && allAccountsMap[item.sourceAccountId]">{{ allAccountsMap[item.sourceAccountId].name }}</span>
|
||||||
<div class="text-error font-italic" v-else>
|
<div class="text-error font-italic" v-else>
|
||||||
@@ -403,12 +431,15 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<replace-invalid-item-dialog ref="replaceInvalidItemDialog" />
|
||||||
<confirm-dialog ref="confirmDialog"/>
|
<confirm-dialog ref="confirmDialog"/>
|
||||||
<snack-bar ref="snackbar" />
|
<snack-bar ref="snackbar" />
|
||||||
<input ref="fileInput" type="file" style="display: none" :accept="supportedImportFileExtensions" @change="setImportFile($event)" />
|
<input ref="fileInput" type="file" style="display: none" :accept="supportedImportFileExtensions" @change="setImportFile($event)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ReplaceInvalidItemDialog from './ReplaceInvalidItemDialog.vue';
|
||||||
|
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/setting.js';
|
import { useSettingsStore } from '@/stores/setting.js';
|
||||||
import { useUserStore } from '@/stores/user.js';
|
import { useUserStore } from '@/stores/user.js';
|
||||||
@@ -438,6 +469,8 @@ import {
|
|||||||
} from '@/lib/category.js';
|
} from '@/lib/category.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
mdiDotsVertical,
|
||||||
|
mdiFindReplace,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiArrowRight,
|
mdiArrowRight,
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
@@ -450,6 +483,9 @@ import {
|
|||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ReplaceInvalidItemDialog
|
||||||
|
},
|
||||||
props: [
|
props: [
|
||||||
'persistent'
|
'persistent'
|
||||||
],
|
],
|
||||||
@@ -473,6 +509,8 @@ export default {
|
|||||||
resolve: null,
|
resolve: null,
|
||||||
reject: null,
|
reject: null,
|
||||||
icons: {
|
icons: {
|
||||||
|
more: mdiDotsVertical,
|
||||||
|
replace: mdiFindReplace,
|
||||||
previous: mdiClose,
|
previous: mdiClose,
|
||||||
next: mdiArrowRight,
|
next: mdiArrowRight,
|
||||||
complete: mdiCheck,
|
complete: mdiCheck,
|
||||||
@@ -598,9 +636,9 @@ export default {
|
|||||||
{ value: 'valid', sortable: true, nowrap: true, width: 35 },
|
{ value: 'valid', sortable: true, nowrap: true, width: 35 },
|
||||||
{ value: 'time', title: this.$t('Transaction Time'), sortable: true, nowrap: true, maxWidth: 280 },
|
{ value: 'time', title: this.$t('Transaction Time'), sortable: true, nowrap: true, maxWidth: 280 },
|
||||||
{ value: 'type', title: this.$t('Type'), sortable: true, nowrap: true, maxWidth: 140 },
|
{ value: 'type', title: this.$t('Type'), sortable: true, nowrap: true, maxWidth: 140 },
|
||||||
{ value: 'categoryId', title: this.$t('Category'), sortable: true, nowrap: true },
|
{ value: 'originalCategoryName', title: this.$t('Category'), sortable: true, nowrap: true },
|
||||||
{ value: 'sourceAmount', title: this.$t('Amount'), sortable: true, nowrap: true },
|
{ value: 'sourceAmount', title: this.$t('Amount'), sortable: true, nowrap: true },
|
||||||
{ value: 'sourceAccountId', title: this.$t('Account'), sortable: true, nowrap: true },
|
{ value: 'originalSourceAccountName', title: this.$t('Account'), sortable: true, nowrap: true },
|
||||||
{ value: 'geoLocation', title: this.$t('Geographic Location'), sortable: true, nowrap: true },
|
{ value: 'geoLocation', title: this.$t('Geographic Location'), sortable: true, nowrap: true },
|
||||||
{ value: 'tagIds', title: this.$t('Tags'), sortable: true, nowrap: true },
|
{ value: 'tagIds', title: this.$t('Tags'), sortable: true, nowrap: true },
|
||||||
{ value: 'comment', title: this.$t('Description'), sortable: true, nowrap: true },
|
{ value: 'comment', title: this.$t('Description'), sortable: true, nowrap: true },
|
||||||
@@ -689,6 +727,21 @@ export default {
|
|||||||
get: function () {
|
get: function () {
|
||||||
return this.selectedImportTransactionCount === this.importTransactions.length;
|
return this.selectedImportTransactionCount === this.importTransactions.length;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
allInvalidExpenseCategoryNames() {
|
||||||
|
return this.getCurrentInvalidCategoryNames(this.allTransactionTypes.Expense);
|
||||||
|
},
|
||||||
|
allInvalidIncomeCategoryNames() {
|
||||||
|
return this.getCurrentInvalidCategoryNames(this.allTransactionTypes.Income);
|
||||||
|
},
|
||||||
|
allInvalidTransferCategoryNames() {
|
||||||
|
return this.getCurrentInvalidCategoryNames(this.allTransactionTypes.Transfer);
|
||||||
|
},
|
||||||
|
allInvalidAccountNames() {
|
||||||
|
return this.getCurrentInvalidAccountNames();
|
||||||
|
},
|
||||||
|
allInvalidTransactionTagNames() {
|
||||||
|
return this.getCurrentInvalidTagNames();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -914,6 +967,175 @@ export default {
|
|||||||
updateTransactionData(transaction) {
|
updateTransactionData(transaction) {
|
||||||
transaction.valid = this.isTransactionValid(transaction);
|
transaction.valid = this.isTransactionValid(transaction);
|
||||||
},
|
},
|
||||||
|
showReplaceInvalidItemDialog(type, invalidItems) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$refs.replaceInvalidItemDialog.open({
|
||||||
|
type: type,
|
||||||
|
invalidItems: invalidItems
|
||||||
|
}).then(result => {
|
||||||
|
if (!result || (!result.sourceItem && result.sourceItem !== '') || !result.targetItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < self.importTransactions.length; i++) {
|
||||||
|
const transaction = self.importTransactions[i];
|
||||||
|
|
||||||
|
if (transaction.valid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
if (type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory') {
|
||||||
|
const categoryId = transaction.categoryId;
|
||||||
|
const originalCategoryName = transaction.originalCategoryName;
|
||||||
|
|
||||||
|
if (transaction.type !== self.allTransactionTypes.ModifyBalance && originalCategoryName === result.sourceItem && (!categoryId || categoryId === '0' || !self.allCategoriesMap[categoryId])) {
|
||||||
|
if (type === 'expenseCategory' && transaction.type === self.allTransactionTypes.Expense) {
|
||||||
|
transaction.categoryId = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
} else if (type === 'incomeCategory' && transaction.type === self.allTransactionTypes.Income) {
|
||||||
|
transaction.categoryId = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
} else if (type === 'transferCategory' && transaction.type === self.allTransactionTypes.Transfer) {
|
||||||
|
transaction.categoryId = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === 'account') {
|
||||||
|
const sourceAccountId = transaction.sourceAccountId;
|
||||||
|
const originalSourceAccountName = transaction.originalSourceAccountName;
|
||||||
|
const destinationAccountId = transaction.destinationAccountId;
|
||||||
|
const originalDestinationAccountName = transaction.originalDestinationAccountName;
|
||||||
|
|
||||||
|
if (originalSourceAccountName === result.sourceItem && (!sourceAccountId || sourceAccountId === '0' || !self.allAccountsMap[sourceAccountId])) {
|
||||||
|
transaction.sourceAccountId = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.type === self.allTransactionTypes.Transfer && originalDestinationAccountName === result.sourceItem && (!destinationAccountId || destinationAccountId === '0' || !self.allAccountsMap[destinationAccountId])) {
|
||||||
|
transaction.destinationAccountId = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
} else if (type === 'tag') {
|
||||||
|
for (let j = 0; j < transaction.tagIds.length; j++) {
|
||||||
|
const tagId = transaction.tagIds[j];
|
||||||
|
const originalTagName = transaction.originalTagNames[j];
|
||||||
|
|
||||||
|
if (originalTagName === result.sourceItem && (!tagId || tagId === '0' || !self.allTagsMap[tagId])) {
|
||||||
|
transaction.tagIds[j] = result.targetItem;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
updatedCount++;
|
||||||
|
self.updateTransactionData(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedCount > 0) {
|
||||||
|
self.$refs.snackbar.showMessage('format.misc.youHaveUpdatedTransactions', {
|
||||||
|
count: updatedCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCurrentInvalidCategoryNames(transactionType) {
|
||||||
|
const invalidCategoryNames = {};
|
||||||
|
const invalidCategories = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.importTransactions.length; i++) {
|
||||||
|
const transaction = this.importTransactions[i];
|
||||||
|
const categoryId = transaction.categoryId;
|
||||||
|
|
||||||
|
if (transaction.type === transactionType && (!categoryId || categoryId === '0' || !this.allCategoriesMap[categoryId])) {
|
||||||
|
invalidCategoryNames[transaction.originalCategoryName] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name in invalidCategoryNames) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(invalidCategoryNames, name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidCategories.push({
|
||||||
|
name: name || this.$t('(Empty)'),
|
||||||
|
value: name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidCategories;
|
||||||
|
},
|
||||||
|
getCurrentInvalidAccountNames() {
|
||||||
|
const invalidAccountNames = {};
|
||||||
|
const invalidAccounts = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.importTransactions.length; i++) {
|
||||||
|
const transaction = this.importTransactions[i];
|
||||||
|
const sourceAccountId = transaction.sourceAccountId;
|
||||||
|
const destinationAccountId = transaction.destinationAccountId;
|
||||||
|
|
||||||
|
if (!sourceAccountId || sourceAccountId === '0' || !this.allAccountsMap[sourceAccountId]) {
|
||||||
|
invalidAccountNames[transaction.originalSourceAccountName] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.type === this.allTransactionTypes.Transfer && (!destinationAccountId || destinationAccountId === '0' || !this.allAccountsMap[destinationAccountId])) {
|
||||||
|
invalidAccountNames[transaction.originalDestinationAccountName] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name in invalidAccountNames) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(invalidAccountNames, name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidAccounts.push({
|
||||||
|
name: name || this.$t('(Empty)'),
|
||||||
|
value: name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidAccounts;
|
||||||
|
},
|
||||||
|
getCurrentInvalidTagNames() {
|
||||||
|
const invalidTagNames = {};
|
||||||
|
const invalidTags = [];
|
||||||
|
|
||||||
|
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]) {
|
||||||
|
invalidTagNames[originalTagName] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name in invalidTagNames) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(invalidTagNames, name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidTags.push({
|
||||||
|
name: name || this.$t('(Empty)'),
|
||||||
|
value: name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidTags;
|
||||||
|
},
|
||||||
isTransactionValid(transaction) {
|
isTransactionValid(transaction) {
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog width="430" :persistent="!!persistent" v-model="showState">
|
||||||
|
<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>
|
||||||
|
</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-autocomplete
|
||||||
|
item-title="name"
|
||||||
|
item-value="value"
|
||||||
|
persistent-placeholder
|
||||||
|
:label="$t('Invalid Category')"
|
||||||
|
:placeholder="$t('Invalid Category')"
|
||||||
|
:items="invalidItems"
|
||||||
|
:no-data-text="$t('No available category')"
|
||||||
|
v-model="sourceItem">
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
||||||
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
||||||
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
|
secondary-hidden-field="hidden"
|
||||||
|
:disabled="!hasAvailableExpenseCategories"
|
||||||
|
:show-selection-primary-text="true"
|
||||||
|
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Expense])"
|
||||||
|
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Expense])"
|
||||||
|
:label="$t('Target Category')"
|
||||||
|
:placeholder="$t('Target Category')"
|
||||||
|
:items="allCategories[allCategoryTypes.Expense]"
|
||||||
|
v-model="targetItem"
|
||||||
|
v-if="type === 'expenseCategory'">
|
||||||
|
</two-column-select>
|
||||||
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
||||||
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
||||||
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
|
secondary-hidden-field="hidden"
|
||||||
|
:disabled="!hasAvailableIncomeCategories"
|
||||||
|
:show-selection-primary-text="true"
|
||||||
|
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Income])"
|
||||||
|
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Income])"
|
||||||
|
:label="$t('Target Category')"
|
||||||
|
:placeholder="$t('Target Category')"
|
||||||
|
:items="allCategories[allCategoryTypes.Income]"
|
||||||
|
v-model="targetItem"
|
||||||
|
v-if="type === 'incomeCategory'">
|
||||||
|
</two-column-select>
|
||||||
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
||||||
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
||||||
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
|
secondary-hidden-field="hidden"
|
||||||
|
:disabled="!hasAvailableTransferCategories"
|
||||||
|
:show-selection-primary-text="true"
|
||||||
|
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Transfer])"
|
||||||
|
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Transfer])"
|
||||||
|
:label="$t('Target Category')"
|
||||||
|
:placeholder="$t('Target Category')"
|
||||||
|
:items="allCategories[allCategoryTypes.Transfer]"
|
||||||
|
v-model="targetItem"
|
||||||
|
v-if="type === 'transferCategory'">
|
||||||
|
</two-column-select>
|
||||||
|
</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-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-autocomplete
|
||||||
|
item-title="name"
|
||||||
|
item-value="value"
|
||||||
|
persistent-placeholder
|
||||||
|
:label="$t('Invalid Account')"
|
||||||
|
:placeholder="$t('Invalid Account')"
|
||||||
|
:items="invalidItems"
|
||||||
|
:no-data-text="$t('No available account')"
|
||||||
|
v-model="sourceItem">
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<two-column-select primary-key-field="id" primary-value-field="category"
|
||||||
|
primary-title-field="name" primary-footer-field="displayBalance"
|
||||||
|
primary-icon-field="icon" primary-icon-type="account"
|
||||||
|
primary-sub-items-field="accounts"
|
||||||
|
:primary-title-i18n="true"
|
||||||
|
secondary-key-field="id" secondary-value-field="id"
|
||||||
|
secondary-title-field="name" secondary-footer-field="displayBalance"
|
||||||
|
secondary-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
|
||||||
|
:disabled="!allVisibleAccounts.length"
|
||||||
|
:custom-selection-primary-text="getAccountDisplayName(targetItem)"
|
||||||
|
:label="$t('Target Account')"
|
||||||
|
:placeholder="$t('Target Account')"
|
||||||
|
:items="allVisibleCategorizedAccounts"
|
||||||
|
v-model="targetItem">
|
||||||
|
</two-column-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</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-autocomplete
|
||||||
|
item-title="name"
|
||||||
|
item-value="value"
|
||||||
|
persistent-placeholder
|
||||||
|
:label="$t('Invalid Tag')"
|
||||||
|
:placeholder="$t('Invalid Tag')"
|
||||||
|
:items="invalidItems"
|
||||||
|
:no-data-text="$t('No available tag')"
|
||||||
|
v-model="sourceItem">
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-autocomplete
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
persistent-placeholder
|
||||||
|
chips
|
||||||
|
:label="$t('Target Tag')"
|
||||||
|
:placeholder="$t('Target Tag')"
|
||||||
|
:items="allTags"
|
||||||
|
:no-data-text="$t('No available tag')"
|
||||||
|
v-model="targetItem"
|
||||||
|
>
|
||||||
|
<template #chip="{ props, item }">
|
||||||
|
<v-chip :prepend-icon="icons.tag" :text="item.title" v-bind="props"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item="{ props, item }">
|
||||||
|
<v-list-item :value="item.value" v-bind="props" v-if="!item.raw.hidden">
|
||||||
|
<template #title>
|
||||||
|
<v-list-item-title>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon size="20" start :icon="icons.tag"/>
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
</v-list-item-title>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</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 color="secondary" variant="tonal" @click="cancel">{{ $t('Cancel') }}</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia';
|
||||||
|
import { useSettingsStore } from '@/stores/setting.js';
|
||||||
|
import { useUserStore } from '@/stores/user.js';
|
||||||
|
import { useAccountsStore } from '@/stores/account.js';
|
||||||
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
|
||||||
|
import { useTransactionTagsStore } from '@/stores/transactionTag.js';
|
||||||
|
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||||
|
|
||||||
|
import categoryConstants from '@/consts/category.js';
|
||||||
|
import {
|
||||||
|
getNameByKeyValue
|
||||||
|
} from '@/lib/common.js';
|
||||||
|
import {
|
||||||
|
getTransactionPrimaryCategoryName,
|
||||||
|
getTransactionSecondaryCategoryName,
|
||||||
|
getFirstAvailableCategoryId
|
||||||
|
} from '@/lib/category.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
mdiPound
|
||||||
|
} from '@mdi/js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'persistent'
|
||||||
|
],
|
||||||
|
expose: [
|
||||||
|
'open'
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showState: false,
|
||||||
|
type: '',
|
||||||
|
invalidItems: [],
|
||||||
|
sourceItem: null,
|
||||||
|
targetItem: null,
|
||||||
|
icons: {
|
||||||
|
tag: mdiPound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useExchangeRatesStore),
|
||||||
|
defaultCurrency() {
|
||||||
|
return this.userStore.currentUserDefaultCurrency;
|
||||||
|
},
|
||||||
|
allCategoryTypes() {
|
||||||
|
return categoryConstants.allCategoryTypes;
|
||||||
|
},
|
||||||
|
allAccounts() {
|
||||||
|
return this.accountsStore.allPlainAccounts;
|
||||||
|
},
|
||||||
|
allVisibleCategorizedAccounts() {
|
||||||
|
return this.$locale.getCategorizedAccountsWithDisplayBalance(this.allVisibleAccounts, this.showAccountBalance, this.defaultCurrency, this.settingsStore, this.userStore, this.exchangeRatesStore);
|
||||||
|
},
|
||||||
|
allVisibleAccounts() {
|
||||||
|
return this.accountsStore.allVisiblePlainAccounts;
|
||||||
|
},
|
||||||
|
allCategories() {
|
||||||
|
return this.transactionCategoriesStore.allTransactionCategories;
|
||||||
|
},
|
||||||
|
allTags() {
|
||||||
|
return this.transactionTagsStore.allTransactionTags;
|
||||||
|
},
|
||||||
|
allTagsMap() {
|
||||||
|
return this.transactionTagsStore.allTransactionTagsMap;
|
||||||
|
},
|
||||||
|
hasAvailableExpenseCategories() {
|
||||||
|
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Expense]);
|
||||||
|
return firstAvailableCategoryId !== '';
|
||||||
|
},
|
||||||
|
hasAvailableIncomeCategories() {
|
||||||
|
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Income] || !this.allCategories[this.allCategoryTypes.Income].length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Income]);
|
||||||
|
return firstAvailableCategoryId !== '';
|
||||||
|
},
|
||||||
|
hasAvailableTransferCategories() {
|
||||||
|
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Transfer] || !this.allCategories[this.allCategoryTypes.Transfer].length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Transfer]);
|
||||||
|
return firstAvailableCategoryId !== '';
|
||||||
|
},
|
||||||
|
showAccountBalance() {
|
||||||
|
return this.settingsStore.appSettings.showAccountBalance;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(options) {
|
||||||
|
const self = this;
|
||||||
|
self.type = options.type;
|
||||||
|
self.invalidItems = options.invalidItems;
|
||||||
|
self.sourceItem = null;
|
||||||
|
self.targetItem = null;
|
||||||
|
self.showState = true;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
self.resolve = resolve;
|
||||||
|
self.reject = reject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
if (this.resolve) {
|
||||||
|
this.resolve({
|
||||||
|
sourceItem: this.sourceItem,
|
||||||
|
targetItem: this.targetItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showState = false;
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
if (this.reject) {
|
||||||
|
this.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showState = false;
|
||||||
|
},
|
||||||
|
getPrimaryCategoryName(categoryId, allCategories) {
|
||||||
|
return getTransactionPrimaryCategoryName(categoryId, allCategories);
|
||||||
|
},
|
||||||
|
getSecondaryCategoryName(categoryId, allCategories) {
|
||||||
|
return getTransactionSecondaryCategoryName(categoryId, allCategories);
|
||||||
|
},
|
||||||
|
getAccountDisplayName(accountId) {
|
||||||
|
if (accountId) {
|
||||||
|
return getNameByKeyValue(this.allAccounts, accountId, 'id', 'name');
|
||||||
|
} else {
|
||||||
|
return this.$t('None');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user