batch create nonexistent transaction tags when import transaction

This commit is contained in:
MaysWind
2025-03-29 21:02:56 +08:00
parent 94ef7f450b
commit 91b6047f2e
9 changed files with 248 additions and 4 deletions
+4
View File
@@ -80,6 +80,7 @@ import type {
} from '@/models/transaction_picture_info.ts';
import type {
TransactionTagCreateRequest,
TransactionTagCreateBatchRequest,
TransactionTagModifyRequest,
TransactionTagHideRequest,
TransactionTagMoveRequest,
@@ -523,6 +524,9 @@ export default {
addTransactionTag: (req: TransactionTagCreateRequest): ApiResponsePromise<TransactionTagInfoResponse> => {
return axios.post<ApiResponse<TransactionTagInfoResponse>>('v1/transaction/tags/add.json', req);
},
addTransactionTagBatch: (req: TransactionTagCreateBatchRequest): ApiResponsePromise<TransactionTagInfoResponse[]> => {
return axios.post<ApiResponse<TransactionTagInfoResponse[]>>('v1/transaction/tags/add_batch.json', req);
},
modifyTransactionTag: (req: TransactionTagModifyRequest): ApiResponsePromise<TransactionTagInfoResponse> => {
return axios.post<ApiResponse<TransactionTagInfoResponse>>('v1/transaction/tags/modify.json', req);
},
+5
View File
@@ -47,6 +47,11 @@ export interface TransactionTagCreateRequest {
readonly name: string;
}
export interface TransactionTagCreateBatchRequest {
readonly tags: TransactionTagCreateRequest[];
readonly skipExists: boolean;
}
export interface TransactionTagModifyRequest {
readonly id: string;
readonly name: string;
+33
View File
@@ -4,6 +4,7 @@ import { defineStore } from 'pinia';
import type { BeforeResolveFunction } from '@/core/base.ts';
import {
type TransactionTagCreateBatchRequest,
type TransactionTagInfoResponse,
type TransactionTagNewDisplayOrderRequest,
TransactionTag
@@ -189,6 +190,37 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
});
}
function addTags(req: TransactionTagCreateBatchRequest): Promise<TransactionTag[]> {
return new Promise((resolve, reject) => {
services.addTransactionTagBatch(req).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to add tag' });
return;
}
if (!transactionTagListStateInvalid.value) {
updateTransactionTagListInvalidState(true);
}
const transactionTags = TransactionTag.ofMulti(data.result);
resolve(transactionTags);
}).catch(error => {
logger.error('failed to add tags', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to add tag' });
} else {
reject(error);
}
});
});
}
function changeTagDisplayOrder({ tagId, from, to }: { tagId: string, from: number, to: number }): Promise<void> {
return new Promise((resolve, reject) => {
let tag: TransactionTag | null = null;
@@ -342,6 +374,7 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
resetTransactionTags,
loadAllTags,
saveTag,
addTags,
changeTagDisplayOrder,
updateTagDisplayOrders,
hideTag,
@@ -155,10 +155,10 @@
:disabled="!!editingTransaction || !allInvalidTransferCategoryNames || allInvalidTransferCategoryNames.length < 1"
:title="tt('Create Nonexistent Transfer Categories')"
@click="showBatchCreateInvalidItemDialog('transferCategory', allInvalidTransferCategoryNames)"></v-list-item>
<!-- <v-list-item :prepend-icon="mdiShapePlusOutline"-->
<!-- :disabled="!!editingTransaction || !allInvalidTransactionTagNames || allInvalidTransactionTagNames.length < 1"-->
<!-- :title="tt('Create Nonexistent Transaction Tags')"-->
<!-- @click="showBatchCreateInvalidItemDialog('tag', allInvalidTransactionTagNames)"></v-list-item>-->
<v-list-item :prepend-icon="mdiShapePlusOutline"
:disabled="!!editingTransaction || !allInvalidTransactionTagNames || allInvalidTransactionTagNames.length < 1"
:title="tt('Create Nonexistent Transaction Tags')"
@click="showBatchCreateInvalidItemDialog('tag', allInvalidTransactionTagNames)"></v-list-item>
<v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiTransfer"
:disabled="!!editingTransaction || selectedExpenseTransactionCount < 1"
@@ -72,6 +72,7 @@ import { ref, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import type { NameValue } from '@/core/base.ts';
import { CategoryType } from '@/core/category.ts';
@@ -79,6 +80,7 @@ import { AUTOMATICALLY_CREATED_CATEGORY_ICON_ID } from '@/consts/icon.ts';
import { DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
import { type TransactionCategoryCreateRequest, type TransactionCategoryCreateWithSubCategories, TransactionCategory } from '@/models/transaction_category.ts';
import { type TransactionTagCreateRequest, TransactionTag } from '@/models/transaction_tag.ts';
import { isDefined, arrayItemToObjectField } from '@/lib/common.ts';
@@ -104,6 +106,7 @@ defineProps<{
const { tt } = useI18n();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTagsStore = useTransactionTagsStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
@@ -151,6 +154,31 @@ function buildBatchCreateCategoryResponse(createdCategories: Record<number, Tran
return response;
}
function buildBatchCreateTagResponse(createdTags: TransactionTag[]): BatchCreateDialogResponse {
const displayNameSourceItemMap: Record<string, string> = {};
const sourceTargetMap: Record<string, string> = {};
for (const item of (invalidItems.value || [])) {
displayNameSourceItemMap[item.name] = item.value;
}
for (const tag of createdTags) {
const sourceItem = displayNameSourceItemMap[tag.name];
if (!isDefined(sourceItem)) {
continue;
}
sourceTargetMap[sourceItem] = tag.id;
}
const response: BatchCreateDialogResponse = {
sourceTargetMap: sourceTargetMap
};
return response;
}
function open(options: { type: BatchCreateDialogDataType, invalidItems?: NameValue[] }): Promise<BatchCreateDialogResponse> {
type.value = options.type;
invalidItems.value = options.invalidItems;
@@ -241,7 +269,38 @@ function confirm(): void {
}
});
} else if (type.value === 'tag') {
submitting.value = true;
const submitTags: TransactionTagCreateRequest[] = [];
for (const item of selectedNames.value) {
const tag: TransactionTag = TransactionTag.createNewTag(item);
submitTags.push(tag.toCreateRequest());
}
transactionTagsStore.addTags({
tags: submitTags,
skipExists: true
}).then(response => {
transactionTagsStore.loadAllTags({ force: false }).then(() => {
submitting.value = false;
showState.value = false;
resolveFunc?.(buildBatchCreateTagResponse(response));
}).catch(error => {
submitting.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}).catch(error => {
submitting.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
}