mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-13 22:47:33 +08:00
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 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,
|
||||
|
||||
Reference in New Issue
Block a user