load / save rules for batch replacing transaction categories / accounts / tags when import transactions
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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": "交易分类不能为空",
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user