load / save rules for batch replacing transaction categories / accounts / tags when import transactions

This commit is contained in:
MaysWind
2025-07-20 01:51:20 +08:00
parent 428bcba56e
commit 8da4f65048
14 changed files with 779 additions and 11 deletions
+97
View File
@@ -325,3 +325,100 @@ export class ImportTransactionDataMapping {
}
}
}
export type ImportTransactionReplaceRuleDataType = 'expenseCategory' | 'incomeCategory' | 'transferCategory' | 'account' | 'tag';
export class ImportTransactionReplaceRule {
public dataType: ImportTransactionReplaceRuleDataType;
public sourceValue: string;
public targetId: string;
private constructor(dataType: ImportTransactionReplaceRuleDataType, sourceValue: string, targetId: string) {
this.dataType = dataType;
this.sourceValue = sourceValue;
this.targetId = targetId;
}
public toJsonObject(): unknown {
return {
type: this.dataType,
sourceValue: this.sourceValue,
targetId: this.targetId
};
}
public static of(dataType: ImportTransactionReplaceRuleDataType, sourceValue: string, targetId: string): ImportTransactionReplaceRule {
return new ImportTransactionReplaceRule(dataType, sourceValue, targetId);
}
public static parse(data: unknown): ImportTransactionReplaceRule | null {
if (!data
|| typeof(data) !== 'object' || !('type' in data) || !('sourceValue' in data) || !('targetId' in data)
|| typeof(data.type) !== 'string' || typeof(data.sourceValue) !== 'string' || typeof(data.targetId) !== 'string') {
return null;
}
if (data.type !== 'expenseCategory' && data.type !== 'incomeCategory' && data.type !== 'transferCategory' && data.type !== 'account' && data.type !== 'tag') {
return null;
}
return new ImportTransactionReplaceRule(data.type as ImportTransactionReplaceRuleDataType, data.sourceValue as string, data.targetId as string);
}
}
export class ImportTransactionReplaceRules {
private static readonly JSON_ROOT_FIELD = 'ezBookkeepingImportTransactionReplaceRules';
private readonly rules: ImportTransactionReplaceRule[];
private constructor(rules: ImportTransactionReplaceRule[]) {
this.rules = rules;
}
public getRules(): ImportTransactionReplaceRule[] {
return this.rules;
}
public toJson(): string {
const result: unknown[] = [];
for (let i = 0; i < this.rules.length; i++) {
const rule = this.rules[i];
result.push(rule.toJsonObject());
}
return JSON.stringify({
[ImportTransactionReplaceRules.JSON_ROOT_FIELD]: result
});
}
public static of(rules: ImportTransactionReplaceRule[]): ImportTransactionReplaceRules {
return new ImportTransactionReplaceRules(rules);
}
public static parseFromJson(json: string): ImportTransactionReplaceRules | null {
try {
const parsed = JSON.parse(json);
const root = parsed[ImportTransactionReplaceRules.JSON_ROOT_FIELD];
if (!root || !('length' in root)) {
return null;
}
const result = new ImportTransactionReplaceRules([]);
for (let i = 0; i < root.length; i++) {
const rule = root[i];
const replaceRule = ImportTransactionReplaceRule.parse(rule);
if (replaceRule) {
result.rules.push(replaceRule);
}
}
return result;
} catch {
return null;
}
}
}
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Kategorie",
"Secondary Category": "Secondary Category",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Mehrere Kategorien",
"Account": "Konto",
"Multiple Accounts": "Mehrere Konten",
"Source Account": "Quellkonto",
"Destination Account": "Zielkonto",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Ohne Tags",
"With Any Selected Tags": "Mit ausgewählten Tags",
"With All Selected Tags": "Mit allen ausgewählten Tags",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Ungültige Überweisungskategorien ersetzen",
"Replace Invalid Accounts": "Ungültige Konten ersetzen",
"Replace Invalid Transaction Tags": "Ungültige Transaktions-Tags ersetzen",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Ungültiges Tag",
"Target Tag": "Ziel-Tag",
"(Empty)": "(Leer)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Tags",
"Your transaction description (optional)": "Ihre Transaktionsbeschreibung (optional)",
"Transaction category cannot be blank": "Transaktionskategorie darf nicht leer sein",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Category",
"Secondary Category": "Secondary Category",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Multiple Categories",
"Account": "Account",
"Multiple Accounts": "Multiple Accounts",
"Source Account": "Source Account",
"Destination Account": "Destination Account",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Without Tags",
"With Any Selected Tags": "With Any Selected Tags",
"With All Selected Tags": "With All Selected Tags",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Replace Invalid Transfer Categories",
"Replace Invalid Accounts": "Replace Invalid Accounts",
"Replace Invalid Transaction Tags": "Replace Invalid Transaction Tags",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Invalid Tag",
"Target Tag": "Target Tag",
"(Empty)": "(Empty)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Tags",
"Your transaction description (optional)": "Your transaction description (optional)",
"Transaction category cannot be blank": "Transaction category cannot be blank",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Categoría",
"Secondary Category": "Secondary Category",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Múltiples categorías",
"Account": "Cuenta",
"Multiple Accounts": "Varias cuentas",
"Source Account": "Cuenta de origen",
"Destination Account": "Cuenta de destino",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Sin Etiquetas",
"With Any Selected Tags": "Con alguna de las etiquetas seleccionada",
"With All Selected Tags": "Con todas las etiquetas seleccionadas",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Reemplazar categorías de transferencia no válidas",
"Replace Invalid Accounts": "Reemplazar cuentas no válidas",
"Replace Invalid Transaction Tags": "Reemplazar etiquetas de transacciones no válidas",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Etiqueta no válida",
"Target Tag": "Etiqueta de destino",
"(Empty)": "(Vacío)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Etiquetas",
"Your transaction description (optional)": "Descripción de su transacción (opcional)",
"Transaction category cannot be blank": "La categoría de transacción no puede estar en blanco",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplica (con ora e posizione geografica)",
"Category": "Categoria",
"Secondary Category": "Categoria secondaria",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Categorie multiple",
"Account": "Conto",
"Multiple Accounts": "Conti multipli",
"Source Account": "Conto di origine",
"Destination Account": "Conto di destinazione",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Senza tag",
"With Any Selected Tags": "Con qualsiasi tag selezionato",
"With All Selected Tags": "Con tutti i tag selezionati",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Sostituisci categorie di trasferimento non valide",
"Replace Invalid Accounts": "Sostituisci conti non validi",
"Replace Invalid Transaction Tags": "Sostituisci tag transazione non validi",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Crea categorie di spesa inesistenti",
"Create Nonexistent Income Categories": "Crea categorie di entrata inesistenti",
"Create Nonexistent Transfer Categories": "Crea categorie di trasferimento inesistenti",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Tag non valido",
"Target Tag": "Tag di destinazione",
"(Empty)": "(Vuoto)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Tag",
"Your transaction description (optional)": "Descrizione della transazione (opzionale)",
"Transaction category cannot be blank": "La categoria della transazione non può essere vuota",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_エクスポートデータ",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "複製(時間と地理座標を含む)",
"Category": "カテゴリ",
"Secondary Category": "二次カテゴリ",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "マルチカテゴリ",
"Account": "口座",
"Multiple Accounts": "マルチ口座",
"Source Account": "元口座",
"Destination Account": "宛先口座",
"Transaction Tag": "Transaction Tag",
"Without Tags": "タグなし",
"With Any Selected Tags": "選択したタグを含む",
"With All Selected Tags": "選択したすべてのタグを含む",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "無効な振替カテゴリを置き換えます",
"Replace Invalid Accounts": "無効な口座を置き換えます",
"Replace Invalid Transaction Tags": "無効な取引タグを置き換えます",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "無効なタグ",
"Target Tag": "対象タグ",
"(Empty)": "(空)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "タグ",
"Your transaction description (optional)": "取引の説明(オプション)",
"Transaction category cannot be blank": "取引カテゴリは空欄にできません",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_dados_estatísticos",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_dados_estatísticos",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicar (Com Tempo e Localização Geográfica)",
"Category": "Categoria",
"Secondary Category": "Categoria Secundária",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Várias Categorias",
"Account": "Conta",
"Multiple Accounts": "Várias Contas",
"Source Account": "Conta de Origem",
"Destination Account": "Conta de Destino",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Sem Tags",
"With Any Selected Tags": "Com Quaisquer Tags Selecionadas",
"With All Selected Tags": "Com Todas as Tags Selecionadas",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Substituir Categorias de Transferência Inválidas",
"Replace Invalid Accounts": "Substituir Contas Inválidas",
"Replace Invalid Transaction Tags": "Substituir Tags de Transação Inválidas",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Criar Categorias de Despesa Inexistentes",
"Create Nonexistent Income Categories": "Criar Categorias de Receita Inexistentes",
"Create Nonexistent Transfer Categories": "Criar Categorias de Transferência Inexistentes",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Tag Inválida",
"Target Tag": "Tag Alvo",
"(Empty)": "(Vazio)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Tags",
"Your transaction description (optional)": "Sua descrição da transação (opcional)",
"Transaction category cannot be blank": "A categoria da transação não pode estar em branco",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Категория",
"Secondary Category": "Secondary Category",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Несколько категорий",
"Account": "Счет",
"Multiple Accounts": "Несколько счетов",
"Source Account": "Исходный счет",
"Destination Account": "Целевой счет",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Без тегов",
"With Any Selected Tags": "С любыми выбранными тегами",
"With All Selected Tags": "Со всеми выбранными тегами",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Заменить недействительные категории переводов",
"Replace Invalid Accounts": "Заменить недействительные счета",
"Replace Invalid Transaction Tags": "Заменить недействительные теги транзакций",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Недействительный тег",
"Target Tag": "Целевой тег",
"(Empty)": "(Пусто)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Теги",
"Your transaction description (optional)": "Описание вашей транзакции (необязательно)",
"Transaction category cannot be blank": "Категория транзакции не может быть пустой",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Дублювати (з часом і геолокацією)",
"Category": "Категорія",
"Secondary Category": "Вторинна категорія",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Кілька категорій",
"Account": "Рахунок",
"Multiple Accounts": "Кілька рахунків",
"Source Account": "Вихідний рахунок",
"Destination Account": "Цільовий рахунок",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Без тегів",
"With Any Selected Tags": "З будь-якими вибраними тегами",
"With All Selected Tags": "З усіма вибраними тегами",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Замінити недійсні категорії переказів",
"Replace Invalid Accounts": "Замінити недійсні рахунки",
"Replace Invalid Transaction Tags": "Замінити недійсні теги транзакцій",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Створити відсутні категорії витрат",
"Create Nonexistent Income Categories": "Створити відсутні категорії доходів",
"Create Nonexistent Transfer Categories": "Створити відсутні категорії переказів",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Недійсний тег",
"Target Tag": "Цільовий тег",
"(Empty)": "(Порожньо)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Теги",
"Your transaction description (optional)": "Опис транзакції (необов'язково)",
"Transaction category cannot be blank": "Категорія транзакції не може бути порожньою",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Danh mục",
"Secondary Category": "Secondary Category",
"Expense Category": "Expense Category",
"Income Category": "Income Category",
"Transfer Category": "Transfer Category",
"Multiple Categories": "Nhiều danh mục",
"Account": "Tài khoản",
"Multiple Accounts": "Nhiều tài khoản",
"Source Account": "Tài khoản nguồn",
"Destination Account": "Tài khoản đích",
"Transaction Tag": "Transaction Tag",
"Without Tags": "Không có thẻ",
"With Any Selected Tags": "With Any Selected Tags",
"With All Selected Tags": "With All Selected Tags",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "Thay thế các danh mục chuyển khoản không hợp lệ",
"Replace Invalid Accounts": "Thay thế các tài khoản không hợp lệ",
"Replace Invalid Transaction Tags": "Thay thế các thẻ giao dịch không hợp lệ",
"Batch Replace Categories / Accounts / Tags": "Batch Replace Categories / Accounts / Tags",
"Create Nonexistent Expense Categories": "Create Nonexistent Expense Categories",
"Create Nonexistent Income Categories": "Create Nonexistent Income Categories",
"Create Nonexistent Transfer Categories": "Create Nonexistent Transfer Categories",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "Thẻ không hợp lệ",
"Target Tag": "Thẻ mục tiêu",
"(Empty)": "(Trống)",
"Source Value": "Source Value",
"Target Value": "Target Value",
"Add Rule": "Add Rule",
"Load Replace Rule File": "Load Replace Rule File",
"Save Replace Rule File": "Save Replace Rule File",
"Replace rule file is invalid": "Replace rule file is invalid",
"Tags": "Thẻ",
"Your transaction description (optional)": "Mô tả giao dịch của bạn (tùy chọn)",
"Transaction category cannot be blank": "Danh mục giao dịch không được để trống",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_导出数据",
"defaultExportStatisticsFileName": "ezBookkeeping_统计数据",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_统计数据",
"defaultImportDataMappingFileName": "ezBookkeeping_导入数据映射文件"
"defaultImportDataMappingFileName": "ezBookkeeping_导入数据映射文件",
"defaultImportReplaceRuleFileName": "ezBookkeeping_导入替换规则文件"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "复制 (含时间和地理位置)",
"Category": "分类",
"Secondary Category": "二级分类",
"Expense Category": "支出分类",
"Income Category": "收入分类",
"Transfer Category": "转账分类",
"Multiple Categories": "多个分类",
"Account": "账户",
"Multiple Accounts": "多个账户",
"Source Account": "来源账户",
"Destination Account": "目标账户",
"Transaction Tag": "交易标签",
"Without Tags": "没有标签",
"With Any Selected Tags": "包含任意选中的标签",
"With All Selected Tags": "包含全部选中的标签",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "替换无效的转账分类",
"Replace Invalid Accounts": "替换无效的账户",
"Replace Invalid Transaction Tags": "替换无效的交易标签",
"Batch Replace Categories / Accounts / Tags": "批量替换分类 / 账户 / 标签",
"Create Nonexistent Expense Categories": "创建不存在的支出分类",
"Create Nonexistent Income Categories": "创建不存在的收入分类",
"Create Nonexistent Transfer Categories": "创建不存在的转账分类",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "无效标签",
"Target Tag": "目标标签",
"(Empty)": "(空白)",
"Source Value": "来源值",
"Target Value": "目标值",
"Add Rule": "添加规则",
"Load Replace Rule File": "加载替换规则文件",
"Save Replace Rule File": "保存替换规则文件",
"Replace rule file is invalid": "替换规则文件无效",
"Tags": "标签",
"Your transaction description (optional)": "你的交易描述 (可选)",
"Transaction category cannot be blank": "交易分类不能为空",
+13 -1
View File
@@ -122,7 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_匯出資料",
"defaultExportStatisticsFileName": "ezBookkeeping_統計資料",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_統計資料",
"defaultImportDataMappingFileName": "ezBookkeeping_匯入資料對應檔案"
"defaultImportDataMappingFileName": "ezBookkeeping_匯入資料對應檔案",
"defaultImportReplaceRuleFileName": "ezBookkeeping_匯入替換規則檔案"
},
"datetime": {
"AM": {
@@ -1653,11 +1654,15 @@
"Duplicate (With Time and Geographic Location)": "複製 (含時間和地理位置)",
"Category": "分類",
"Secondary Category": "次分類",
"Expense Category": "支出分類",
"Income Category": "收入分類",
"Transfer Category": "轉帳分類",
"Multiple Categories": "多個分類",
"Account": "帳戶",
"Multiple Accounts": "多個帳戶",
"Source Account": "來源帳戶",
"Destination Account": "目標帳戶",
"Transaction Tag": "交易標籤",
"Without Tags": "沒有標籤",
"With Any Selected Tags": "包含任意選中的標籤",
"With All Selected Tags": "包含全部選中的標籤",
@@ -1750,6 +1755,7 @@
"Replace Invalid Transfer Categories": "替換無效的轉帳分類",
"Replace Invalid Accounts": "替換無效的帳戶",
"Replace Invalid Transaction Tags": "替換無效的交易標籤",
"Batch Replace Categories / Accounts / Tags": "批次替換分類 / 帳戶 / 標籤",
"Create Nonexistent Expense Categories": "建立不存在的支出分類",
"Create Nonexistent Income Categories": "建立不存在的收入分類",
"Create Nonexistent Transfer Categories": "建立不存在的轉帳分類",
@@ -1770,6 +1776,12 @@
"Invalid Tag": "無效標籤",
"Target Tag": "目標標籤",
"(Empty)": "(空白)",
"Source Value": "來源值",
"Target Value": "目標值",
"Add Rule": "新增規則",
"Load Replace Rule File": "載入替換規則檔案",
"Save Replace Rule File": "儲存替換規則檔案",
"Replace rule file is invalid": "替換規則檔案無效",
"Tags": "標籤",
"Your transaction description (optional)": "您的交易描述 (選填)",
"Transaction category cannot be blank": "交易分類不能為空",
@@ -158,6 +158,11 @@
:title="tt('Replace Invalid Transaction Tags')"
@click="showReplaceInvalidItemDialog('tag', allInvalidTransactionTagNames)"></v-list-item>
<v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiFindReplace"
:disabled="!!editingTransaction"
:title="tt('Batch Replace Categories / Accounts / Tags')"
@click="showReplaceAllTypesDialog()"></v-list-item>
<v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiShapePlusOutline"
:disabled="!!editingTransaction || !allInvalidExpenseCategoryNames || allInvalidExpenseCategoryNames.length < 1"
:title="tt('Create Nonexistent Expense Categories')"
@@ -862,6 +867,7 @@
@dateRange:change="changeCustomDateFilter"
@error="onShowDateRangeError" />
<batch-replace-dialog ref="batchReplaceDialog" />
<batch-replace-all-types-dialog ref="batchReplaceAllTypesDialog" />
<batch-create-dialog ref="batchCreateDialog" />
<confirm-dialog ref="confirmDialog"/>
<snack-bar ref="snackbar" />
@@ -874,6 +880,7 @@ import PaginationButtons from '@/components/desktop/PaginationButtons.vue';
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
import SnackBar from '@/components/desktop/SnackBar.vue';
import BatchReplaceDialog, { type BatchReplaceDialogDataType } from './dialogs/BatchReplaceDialog.vue';
import BatchReplaceAllTypesDialog from './dialogs/BatchReplaceAllTypesDialog.vue';
import BatchCreateDialog, { type BatchCreateDialogDataType } from './dialogs/BatchCreateDialog.vue';
import { ref, computed, useTemplateRef, watch } from 'vue';
@@ -962,6 +969,7 @@ import {
type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
type SnackBarType = InstanceType<typeof SnackBar>;
type BatchReplaceDialogType = InstanceType<typeof BatchReplaceDialog>;
type BatchReplaceAllTypesDialogType = InstanceType<typeof BatchReplaceAllTypesDialog>;
type BatchCreateDialogType = InstanceType<typeof BatchCreateDialog>;
type ImportTransactionDialogStep = 'uploadFile' | 'defineColumn' | 'checkData' | 'finalResult';
@@ -1007,6 +1015,7 @@ const statisticsStore = useStatisticsStore();
const confirmDialog = useTemplateRef<ConfirmDialogType>('confirmDialog');
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const batchReplaceDialog = useTemplateRef<BatchReplaceDialogType>('batchReplaceDialog');
const batchReplaceAllTypesDialog = useTemplateRef<BatchReplaceAllTypesDialogType>('batchReplaceAllTypesDialog');
const batchCreateDialog = useTemplateRef<BatchCreateDialogType>('batchCreateDialog');
const fileInput = useTemplateRef<HTMLInputElement>('fileInput');
@@ -2479,6 +2488,78 @@ function showReplaceInvalidItemDialog(type: BatchReplaceDialogDataType, invalidI
});
}
function showReplaceAllTypesDialog(): void {
if (editingTransaction.value) {
return;
}
batchReplaceAllTypesDialog.value?.open({
expenseCategoryNames: allInvalidExpenseCategoryNames.value,
incomeCategoryNames: allInvalidIncomeCategoryNames.value,
transferCategoryNames: allInvalidTransferCategoryNames.value,
accountNames: allInvalidAccountNames.value,
tagNames: allInvalidTransactionTagNames.value
}).then(result => {
if (!result || !result.rules) {
return;
}
let updatedCount = 0;
if (importTransactions.value) {
for (let i = 0; i < importTransactions.value.length; i++) {
const transaction: ImportTransaction = importTransactions.value[i];
let updated = false;
for (let j = 0; j < result.rules.length; j++) {
const rule = result.rules[j];
if (!rule || !rule.dataType || !rule.sourceValue || !rule.targetId) {
continue;
}
if (rule.dataType === 'expenseCategory' || rule.dataType === 'incomeCategory' || rule.dataType === 'transferCategory') {
if (transaction.type !== TransactionType.ModifyBalance && transaction.originalCategoryName === rule.sourceValue) {
transaction.categoryId = rule.targetId;
updated = true;
}
} else if (rule.dataType === 'account') {
if (transaction.originalSourceAccountName === rule.sourceValue) {
transaction.sourceAccountId = rule.targetId;
updated = true;
}
if (transaction.type === TransactionType.Transfer && transaction.originalDestinationAccountName === rule.sourceValue) {
transaction.destinationAccountId = rule.targetId;
updated = true;
}
} else if (rule.dataType === 'tag' && transaction.tagIds) {
for (let k = 0; k < transaction.tagIds.length; k++) {
const originalTagName = transaction.originalTagNames ? transaction.originalTagNames[k] : "";
if (originalTagName === rule.sourceValue) {
transaction.tagIds[k] = rule.targetId;
updated = true;
}
}
}
}
if (updated) {
updatedCount++;
updateTransactionData(transaction);
}
}
}
if (updatedCount > 0) {
snackbar.value?.showMessage('format.misc.youHaveUpdatedTransactions', {
count: updatedCount
});
}
});
}
function showBatchCreateInvalidItemDialog(type: BatchCreateDialogDataType, invalidItems: NameValue[]): void {
if (editingTransaction.value) {
return;
@@ -0,0 +1,458 @@
<template>
<v-dialog width="1000" :persistent="loading || !!rules.length || !!newRule.targetId" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<h4 class="text-h4">{{ tt('Batch Replace Categories / Accounts / Tags') }}</h4>
<v-btn density="compact" color="default" variant="text" size="24"
class="ml-2" :icon="true" :disabled="loading"
:loading="loading" @click="reload">
<template #loader>
<v-progress-circular indeterminate size="20"/>
</template>
<v-icon :icon="mdiRefresh" size="24" />
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
</v-btn>
</div>
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
:icon="true" :disabled="loading">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent" max-height="500">
<v-list>
<v-list-item :prepend-icon="mdiFolderOpenOutline"
:title="tt('Load Replace Rule File')"
@click="loadReplaceRuleFile()"></v-list-item>
<v-list-item :prepend-icon="mdiContentSaveOutline"
:title="tt('Save Replace Rule File')"
@click="saveReplaceRuleFile()"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
</template>
<v-card-text class="my-md-4 w-100 d-flex justify-center">
<v-row>
<v-col cols="12">
<v-table fixed-header fixed-footer height="400" striped="even">
<thead>
<tr>
<th class="text-left">{{ tt('Type') }}</th>
<th class="text-left">{{ tt('Source Value') }}</th>
<th class="text-left">{{ tt('Target Value') }}</th>
<th class="text-right">{{ tt('Operation') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(rule, index) in rules" :key="index">
<td class="text-left">{{ getRuleTypeDisplayName(rule) }}</td>
<td class="text-left">{{ rule.sourceValue || tt('(Empty)') }}</td>
<td class="text-left">{{ getRuleTargetValueDisplayName(rule) }}</td>
<td class="text-right">
<v-btn density="comfortable" variant="tonal" color="error"
:disabled="loading" @click="removeRule(index)">{{ tt('Delete') }}</v-btn>
</td>
</tr>
</tbody>
<tfoot>
<tr style="background-color: rgb(var(--v-theme-surface))">
<td>
<v-select class="w-100" density="compact" variant="outlined"
item-title="name"
item-value="value"
:disabled="loading"
:items="[
{
value: 'expenseCategory',
name: tt('Expense Category')
},
{
value: 'incomeCategory',
name: tt('Income Category')
},
{
value: 'transferCategory',
name: tt('Transfer Category')
},
{
value: 'account',
name: tt('Account')
},
{
value: 'tag',
name: tt('Transaction Tag')
}
]"
v-model="newRule.dataType"
@update:model-value="newRule.sourceValue = ''; newRule.targetId = ''"
/>
</td>
<td>
<v-autocomplete class="w-100" density="compact" variant="outlined"
item-title="name" item-value="value" persistent-placeholder
:disabled="loading" :items="sourceItems"
:no-data-text="noSourceItemText"
v-model="newRule.sourceValue">
</v-autocomplete>
</td>
<td>
<two-column-select density="compact" variant="outlined"
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="loading || !hasAvailableExpenseCategories"
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
:show-selection-primary-text="true"
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Expense])"
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(newRule.targetId, allCategories[CategoryType.Expense])"
:items="allCategories[CategoryType.Expense]"
v-model="newRule.targetId"
v-if="newRule.dataType === 'expenseCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
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="loading || !hasAvailableIncomeCategories"
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
:show-selection-primary-text="true"
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Income])"
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(newRule.targetId, allCategories[CategoryType.Income])"
:items="allCategories[CategoryType.Income]"
v-model="newRule.targetId"
v-if="newRule.dataType === 'incomeCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
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="loading || !hasAvailableTransferCategories"
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
:show-selection-primary-text="true"
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Transfer])"
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(newRule.targetId, allCategories[CategoryType.Transfer])"
:items="allCategories[CategoryType.Transfer]"
v-model="newRule.targetId"
v-if="newRule.dataType === 'transferCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
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="loading || !allVisibleAccounts.length"
:enable-filter="true" :filter-placeholder="tt('Find account')" :filter-no-items-text="tt('No available account')"
:custom-selection-primary-text="getAccountDisplayName(newRule.targetId)"
:items="allVisibleCategorizedAccounts"
v-model="newRule.targetId"
v-if="newRule.dataType === 'account'">
</two-column-select>
<v-autocomplete density="compact" variant="outlined"
item-title="name" item-value="id"
persistent-placeholder chips
:disabled="loading" :items="allTags"
:no-data-text="tt('No available tag')"
v-model="newRule.targetId"
v-if="newRule.dataType == 'tag'">
<template #chip="{ props, item }">
<v-chip :prepend-icon="mdiPound" :text="item.title" v-bind="props" v-if="newRule.targetId"/>
</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="mdiPound"/>
<span>{{ item.title }}</span>
</div>
</v-list-item-title>
</template>
</v-list-item>
</template>
</v-autocomplete>
</td>
<td class="text-right">
<v-btn density="comfortable" variant="tonal" color="primary"
:disabled="loading || !newRule.dataType || !newRule.targetId"
@click="addNewRule()">{{ tt('Add Rule') }}</v-btn>
</td>
</tr>
</tfoot>
</v-table>
</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="loading" @click="confirm">{{ tt('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" :disabled="loading" @click="cancel">{{ tt('Cancel') }}</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
<snack-bar ref="snackbar" />
</template>
<script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, computed, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useSettingsStore } from '@/stores/setting.ts';
import { useAccountsStore } from '@/stores/account.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';
import { ImportTransactionReplaceRule, ImportTransactionReplaceRules } from '@/core/import_transaction.ts';
import { KnownFileType } from '@/core/file.ts';
import { Account, type CategorizedAccountWithDisplayBalance } from '@/models/account.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts';
import type { TransactionTag } from '@/models/transaction_tag.ts';
import {
getTransactionPrimaryCategoryName,
getTransactionSecondaryCategoryName
} from '@/lib/category.ts';
import logger from '@/lib/logger.ts';
import {
openTextFileContent,
startDownloadFile
} from '@/lib/ui/common.ts';
import {
mdiRefresh,
mdiDotsVertical,
mdiFolderOpenOutline,
mdiContentSaveOutline, mdiPound
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
interface BatchReplaceAllTypesDialogResponse {
rules: ImportTransactionReplaceRule[]
}
const { tt, getCategorizedAccountsWithDisplayBalance } = useI18n();
const settingsStore = useSettingsStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTagsStore = useTransactionTagsStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const showState = ref<boolean>(false);
const loading = ref<boolean>(false);
const rules = ref<ImportTransactionReplaceRule[]>([]);
const newRule = ref<ImportTransactionReplaceRule>(ImportTransactionReplaceRule.of('expenseCategory', '', ''));
const sourceExpenseCategoryNames = ref<NameValue[]>([]);
const sourceIncomeCategoryNames = ref<NameValue[]>([]);
const sourceTransferCategoryNames = ref<NameValue[]>([]);
const sourceAccountNames = ref<NameValue[]>([]);
const sourceTagNames = ref<NameValue[]>([]);
let resolveFunc: ((response: BatchReplaceAllTypesDialogResponse) => void) | null = null;
let rejectFunc: ((reason?: unknown) => void) | null = null;
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
const hasAvailableExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableExpenseCategories);
const hasAvailableIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableIncomeCategories);
const hasAvailableTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableTransferCategories);
const sourceItems = computed<NameValue[]>(() => {
switch (newRule.value.dataType) {
case 'expenseCategory':
return sourceExpenseCategoryNames.value;
case 'incomeCategory':
return sourceIncomeCategoryNames.value;
case 'transferCategory':
return sourceTransferCategoryNames.value;
case 'account':
return sourceAccountNames.value;
case 'tag':
return sourceTagNames.value;
default:
return [];
}
});
const noSourceItemText = computed<string>(() => {
switch (newRule.value.dataType) {
case 'expenseCategory':
return tt('No available category');
case 'incomeCategory':
return tt('No available category');
case 'transferCategory':
return tt('No available category');
case 'account':
return tt('No available account');
case 'tag':
return tt('No available tag');
default:
return '';
}
});
function getRuleTypeDisplayName(rule: ImportTransactionReplaceRule): string {
switch (rule.dataType) {
case 'expenseCategory':
return tt('Expense Category');
case 'incomeCategory':
return tt('Income Category');
case 'transferCategory':
return tt('Transfer Category');
case 'account':
return tt('Account');
case 'tag':
return tt('Transaction Tag');
default:
return '';
}
}
function getRuleTargetValueDisplayName(rule: ImportTransactionReplaceRule): string {
switch (rule.dataType) {
case 'expenseCategory':
return getTransactionSecondaryCategoryName(rule.targetId, allCategories.value[CategoryType.Expense]) || '';
case 'incomeCategory':
return getTransactionSecondaryCategoryName(rule.targetId, allCategories.value[CategoryType.Income]) || '';
case 'transferCategory':
return getTransactionSecondaryCategoryName(rule.targetId, allCategories.value[CategoryType.Transfer]) || '';
case 'account':
return getAccountDisplayName(rule.targetId);
case 'tag':
for (const tag of allTags.value) {
if (tag.id === rule.targetId) {
return tag.name;
}
}
return '';
default:
return '';
}
}
function getAccountDisplayName(accountId?: string): string {
if (accountId) {
return Account.findAccountNameById(allAccounts.value, accountId) || '';
} else {
return tt('None');
}
}
function open(options: { expenseCategoryNames: NameValue[], incomeCategoryNames: NameValue[], transferCategoryNames: NameValue[], accountNames: NameValue[], tagNames: NameValue[] }): Promise<BatchReplaceAllTypesDialogResponse> {
rules.value = [];
newRule.value = ImportTransactionReplaceRule.of('expenseCategory', '', '');
sourceExpenseCategoryNames.value = options.expenseCategoryNames;
sourceIncomeCategoryNames.value = options.incomeCategoryNames;
sourceTransferCategoryNames.value = options.transferCategoryNames;
sourceAccountNames.value = options.accountNames;
sourceTagNames.value = options.tagNames;
showState.value = true;
return new Promise((resolve, reject) => {
resolveFunc = resolve;
rejectFunc = reject;
});
}
function reload(): void {
loading.value = true;
Promise.all([
accountsStore.loadAllAccounts({ force: true }),
transactionCategoriesStore.loadAllCategories({ force: true }),
transactionTagsStore.loadAllTags({ force: true })
]).then(() => {
loading.value = false;
}).catch(error => {
loading.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
function loadReplaceRuleFile(): void {
openTextFileContent({
allowedExtensions: KnownFileType.JSON.contentType
}).then(content => {
const result = ImportTransactionReplaceRules.parseFromJson(content);
if (result) {
rules.value = result.getRules();
} else {
logger.error('Failed to parse replace rule file');
snackbar.value?.showError('Replace rule file is invalid');
}
}).catch(error => {
logger.error('Failed to open replace rule file', error);
snackbar.value?.showError('Replace rule file is invalid');
});
}
function saveReplaceRuleFile(): void {
const fileName = KnownFileType.JSON.formatFileName(tt('dataExport.defaultImportReplaceRuleFileName'));
startDownloadFile(fileName, KnownFileType.JSON.createBlob(ImportTransactionReplaceRules.of(rules.value).toJson()));
}
function removeRule(index: number): void {
rules.value.splice(index, 1);
}
function addNewRule(): void {
if (!newRule.value.dataType || !newRule.value.targetId) {
return;
}
rules.value.push(newRule.value);
newRule.value = ImportTransactionReplaceRule.of('expenseCategory', '', '');
}
function confirm(): void {
resolveFunc?.({
rules: rules.value
});
showState.value = false;
}
function cancel(): void {
rejectFunc?.();
showState.value = false;
}
defineExpose({
open
});
</script>