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:
@@ -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 SnackBar from '@/components/desktop/SnackBar.vue';
|
||||||
import PaginationButtons from '@/components/desktop/PaginationButtons.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';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
@@ -235,6 +235,7 @@ import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
|
|||||||
import { TransactionType } from '@/core/transaction.ts';
|
import { TransactionType } from '@/core/transaction.ts';
|
||||||
import { ImportTransactionColumnType, ImportTransactionDataMapping } from '@/core/import_transaction.ts';
|
import { ImportTransactionColumnType, ImportTransactionDataMapping } from '@/core/import_transaction.ts';
|
||||||
import { KnownFileType } from '@/core/file.ts';
|
import { KnownFileType } from '@/core/file.ts';
|
||||||
|
import { KNOWN_COLUMN_NAME_MAPPING, KNOWN_TRANSACTION_TYPE_NAME_MAPPING } from '@/consts/import_transaction.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isNumber,
|
isNumber,
|
||||||
@@ -477,6 +478,10 @@ function getTablePageOptions(linesCount?: number): NameNumeralValue[] {
|
|||||||
return pageOptions;
|
return pageOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNormalizedKey(key: string): string {
|
||||||
|
return key.toLowerCase().replaceAll(' ', '').replaceAll('_', '').replaceAll('-', '');
|
||||||
|
}
|
||||||
|
|
||||||
function getParseDataMappedColumnDisplayName(columnIndex: number): string {
|
function getParseDataMappedColumnDisplayName(columnIndex: number): string {
|
||||||
for (const [columnType, index] of entries(parsedFileDataColumnMapping.value.dataColumnMapping)) {
|
for (const [columnType, index] of entries(parsedFileDataColumnMapping.value.dataColumnMapping)) {
|
||||||
if (index === columnIndex) {
|
if (index === columnIndex) {
|
||||||
@@ -487,6 +492,72 @@ function getParseDataMappedColumnDisplayName(columnIndex: number): string {
|
|||||||
return tt('Unspecified');
|
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 {
|
function generateResult(): ImportTransactionDefineColumnResult | undefined {
|
||||||
const columnMapping: Record<number, number> = parsedFileDataColumnMapping.value.dataColumnMapping;
|
const columnMapping: Record<number, number> = parsedFileDataColumnMapping.value.dataColumnMapping;
|
||||||
const transactionTypeMapping: Record<string, TransactionType> = parsedFileValidMappedTransactionTypes.value;
|
const transactionTypeMapping: Record<string, TransactionType> = parsedFileValidMappedTransactionTypes.value;
|
||||||
@@ -588,6 +659,13 @@ function saveColumnMappingFile(): void {
|
|||||||
startDownloadFile(fileName, KnownFileType.JSON.createBlob(parsedFileDataColumnMapping.value.toJson()));
|
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({
|
defineExpose({
|
||||||
menus,
|
menus,
|
||||||
generateResult,
|
generateResult,
|
||||||
|
|||||||
Reference in New Issue
Block a user