support automatically applying known column mapping and transaction type mapping rules when importing custom files with column mapping handle method

This commit is contained in:
MaysWind
2026-03-01 16:32:22 +08:00
parent d7a0d253c4
commit c6eb3cfb74
2 changed files with 279 additions and 1 deletions
+200
View File
@@ -0,0 +1,200 @@
import { entries } from '@/core/base.ts';
import { TransactionType } from '@/core/transaction.ts';
import { ImportTransactionColumnType } from '@/core/import_transaction.ts';
export const KNOWN_COLUMN_NAME_MAPPING: Record<string, ImportTransactionColumnType> = ((mappings: Record<string, ImportTransactionColumnType>[]) => {
const result: Record<string, ImportTransactionColumnType> = {};
for (const mapping of mappings) {
for (const [key, value] of entries(mapping)) {
const normalizedKey = key.toLowerCase().replaceAll(' ', '').replaceAll('_', '').replaceAll('-', '');
if (result[normalizedKey]) {
continue;
}
result[normalizedKey] = value;
}
}
return result;
})([
// Columns of ezbookkeeping Data Export File
{
['Time']: ImportTransactionColumnType.TransactionTime,
['Timezone']: ImportTransactionColumnType.TransactionTimezone,
['Type']: ImportTransactionColumnType.TransactionType,
['Category']: ImportTransactionColumnType.Category,
['Sub Category']: ImportTransactionColumnType.SubCategory,
['Account']: ImportTransactionColumnType.AccountName,
['Account Currency']: ImportTransactionColumnType.AccountCurrency,
['Amount']: ImportTransactionColumnType.Amount,
['Account2']: ImportTransactionColumnType.RelatedAccountName,
['Account2 Currency']: ImportTransactionColumnType.RelatedAccountCurrency,
['Account2 Amount']: ImportTransactionColumnType.RelatedAmount,
['Geographic Location']: ImportTransactionColumnType.GeographicLocation,
['Tags']: ImportTransactionColumnType.Tags,
['Description']: ImportTransactionColumnType.Description
},
// Other common columns of transaction time
{
// en
['Date']: ImportTransactionColumnType.TransactionTime,
['Datetime']: ImportTransactionColumnType.TransactionTime,
['Timestamp']: ImportTransactionColumnType.TransactionTime,
// zh-Hans
['日期']: ImportTransactionColumnType.TransactionTime,
['时间']: ImportTransactionColumnType.TransactionTime,
['交易日期']: ImportTransactionColumnType.TransactionTime,
['交易时间']: ImportTransactionColumnType.TransactionTime,
},
// Other common columns of transaction timezone
{
},
// Other common columns of transaction type
{
// en
['Transaction Type']: ImportTransactionColumnType.TransactionType,
// zh-Hans
['交易类型']: ImportTransactionColumnType.TransactionType,
['类型']: ImportTransactionColumnType.TransactionType,
['收/支']: ImportTransactionColumnType.TransactionType,
},
// Other common columns of category
{
// en
['Category Name']: ImportTransactionColumnType.Category,
// zh-Hans
['交易分类']: ImportTransactionColumnType.Category,
['类别']: ImportTransactionColumnType.Category,
['分类']: ImportTransactionColumnType.Category,
},
// Other common columns of sub category
{
// zh-Hans
['子类别']: ImportTransactionColumnType.SubCategory,
['子分类']: ImportTransactionColumnType.SubCategory,
['二级分类']: ImportTransactionColumnType.SubCategory,
},
// Other common columns of account name
{
// en
['Account Name']: ImportTransactionColumnType.AccountName,
['Source Name']: ImportTransactionColumnType.AccountName,
// zh-Hans
['账户']: ImportTransactionColumnType.AccountName,
['账户1']: ImportTransactionColumnType.AccountName,
},
// Other common columns of account currency
{
// en
['Currency']: ImportTransactionColumnType.AccountCurrency,
['Currency Code']: ImportTransactionColumnType.AccountCurrency,
// zh-Hans
['账户币种']: ImportTransactionColumnType.AccountCurrency,
['币种']: ImportTransactionColumnType.AccountCurrency,
},
// Other common columns of amount
{
// zh-Hans
['金额']: ImportTransactionColumnType.Amount,
},
// Other common columns of related account name
{
// en
['Destination Name']: ImportTransactionColumnType.RelatedAccountName,
// zh-Hans
['账户2']: ImportTransactionColumnType.RelatedAccountName,
},
// Other common columns of related account currency
{
// en
['Foreign Currency']: ImportTransactionColumnType.RelatedAccountCurrency,
['Foreign Currency Code']: ImportTransactionColumnType.RelatedAccountCurrency,
},
// Other common columns of related amount
{
// en
['Foreign Amount']: ImportTransactionColumnType.RelatedAmount,
},
// Other common columns of geographic location
{
},
// Other common columns of tags
{
// zh-Hans
['标签']: ImportTransactionColumnType.Tags,
},
// Other common columns of description
{
// en
['Comment']: ImportTransactionColumnType.Description,
['Note']: ImportTransactionColumnType.Description,
['Memo']: ImportTransactionColumnType.Description,
// zh-Hans
['备注']: ImportTransactionColumnType.Description,
}
]);
export const KNOWN_TRANSACTION_TYPE_NAME_MAPPING: Record<string, TransactionType> = ((mappings: Record<string, TransactionType>[]) => {
const result: Record<string, TransactionType> = {};
for (const mapping of mappings) {
for (const [key, value] of entries(mapping)) {
const normalizedKey = key.toLowerCase().replaceAll(' ', '').replaceAll('_', '').replaceAll('-', '');
if (result[normalizedKey]) {
continue;
}
result[normalizedKey] = value;
}
}
return result;
})([
// Transaction types of ezbookkeeping Data Export File
{
['Balance Modification']: TransactionType.ModifyBalance,
['Income']: TransactionType.Income,
['Expense']: TransactionType.Expense,
['Transfer']: TransactionType.Transfer,
},
// Other common balance modification type
{
// en
['Opening balance']: TransactionType.ModifyBalance,
// zh-Hans
['余额变更']: TransactionType.ModifyBalance,
['负债变更']: TransactionType.ModifyBalance,
},
// Other common income type
{
// en
['Deposit']: TransactionType.Income,
// zh-Hans
['收入']: TransactionType.Income,
},
// Other common expense type
{
// en
['Withdrawal']: TransactionType.Expense,
// zh-Hans
['支出']: TransactionType.Expense,
},
// Other common transfer type
{
// zh-Hans
['转账']: TransactionType.Transfer,
['还款']: TransactionType.Transfer,
['借入']: TransactionType.Transfer,
['借出']: TransactionType.Transfer,
['收债']: TransactionType.Transfer,
['还债']: TransactionType.Transfer,
['代付']: TransactionType.Transfer,
['报销']: TransactionType.Transfer,
['退款']: TransactionType.Transfer,
}
]);
@@ -224,7 +224,7 @@
import SnackBar from '@/components/desktop/SnackBar.vue';
import PaginationButtons from '@/components/desktop/PaginationButtons.vue';
import { ref, computed, useTemplateRef } from 'vue';
import { ref, computed, useTemplateRef, watch } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
@@ -235,6 +235,7 @@ import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
import { TransactionType } from '@/core/transaction.ts';
import { ImportTransactionColumnType, ImportTransactionDataMapping } from '@/core/import_transaction.ts';
import { KnownFileType } from '@/core/file.ts';
import { KNOWN_COLUMN_NAME_MAPPING, KNOWN_TRANSACTION_TYPE_NAME_MAPPING } from '@/consts/import_transaction.ts';
import {
isNumber,
@@ -477,6 +478,10 @@ function getTablePageOptions(linesCount?: number): NameNumeralValue[] {
return pageOptions;
}
function getNormalizedKey(key: string): string {
return key.toLowerCase().replaceAll(' ', '').replaceAll('_', '').replaceAll('-', '');
}
function getParseDataMappedColumnDisplayName(columnIndex: number): string {
for (const [columnType, index] of entries(parsedFileDataColumnMapping.value.dataColumnMapping)) {
if (index === columnIndex) {
@@ -487,6 +492,72 @@ function getParseDataMappedColumnDisplayName(columnIndex: number): string {
return tt('Unspecified');
}
function autoSetColumnMapping(): void {
if (!props.parsedFileData) {
return;
}
const firstLine: string[] = props.parsedFileData.length > 0 ? (props.parsedFileData[0] as string[]) : [];
const displayColumnNamesMap: Record<string, ImportTransactionColumnType> = {};
for (const column of ImportTransactionColumnType.values()) {
displayColumnNamesMap[getNormalizedKey(tt(column.name))] = column;
}
for (const [columnName, index] of itemAndIndex(firstLine)) {
const normalizedColumnName = getNormalizedKey(columnName);
if (displayColumnNamesMap[normalizedColumnName]) {
const columnType = displayColumnNamesMap[normalizedColumnName];
if (!isNumber(parsedFileDataColumnMapping.value.dataColumnMapping[columnType.type])) {
parsedFileDataColumnMapping.value.dataColumnMapping[columnType.type] = index;
}
continue;
}
if (KNOWN_COLUMN_NAME_MAPPING[normalizedColumnName]) {
const columnType = KNOWN_COLUMN_NAME_MAPPING[normalizedColumnName];
if (!isNumber(parsedFileDataColumnMapping.value.dataColumnMapping[columnType.type])) {
parsedFileDataColumnMapping.value.dataColumnMapping[columnType.type] = index;
}
continue;
}
}
}
function autoSetTransactionTypeMapping(): void {
if (!props.parsedFileData) {
return;
}
const displayTransactinTypeNamesMap: Record<string, TransactionType> = {
[getNormalizedKey(tt('Modify Balance'))]: TransactionType.ModifyBalance,
[getNormalizedKey(tt('Income'))]: TransactionType.Income,
[getNormalizedKey(tt('Expense'))]: TransactionType.Expense,
[getNormalizedKey(tt('Transfer'))]: TransactionType.Transfer
};
for (const transactionTypeName of parsedFileAllTransactionTypes.value) {
const normalizedTransactionTypeName = getNormalizedKey(transactionTypeName);
if (displayTransactinTypeNamesMap[normalizedTransactionTypeName]) {
const transactionType = displayTransactinTypeNamesMap[normalizedTransactionTypeName];
parsedFileDataColumnMapping.value.transactionTypeMapping[transactionTypeName] = transactionType;
continue;
}
if (KNOWN_TRANSACTION_TYPE_NAME_MAPPING[normalizedTransactionTypeName]) {
const transactionType = KNOWN_TRANSACTION_TYPE_NAME_MAPPING[normalizedTransactionTypeName];
parsedFileDataColumnMapping.value.transactionTypeMapping[transactionTypeName] = transactionType;
continue;
}
}
}
function generateResult(): ImportTransactionDefineColumnResult | undefined {
const columnMapping: Record<number, number> = parsedFileDataColumnMapping.value.dataColumnMapping;
const transactionTypeMapping: Record<string, TransactionType> = parsedFileValidMappedTransactionTypes.value;
@@ -588,6 +659,13 @@ function saveColumnMappingFile(): void {
startDownloadFile(fileName, KnownFileType.JSON.createBlob(parsedFileDataColumnMapping.value.toJson()));
}
watch(() => props.parsedFileData, (newValue, oldValue) => {
if (newValue && !oldValue && getObjectOwnFieldCount(parsedFileDataColumnMapping.value.dataColumnMapping) < 1) {
autoSetColumnMapping();
autoSetTransactionTypeMapping();
}
}, { immediate: true });
defineExpose({
menus,
generateResult,