transaction reconciliation statement supports sorting by account name and category name on desktop version
This commit is contained in:
@@ -690,6 +690,22 @@ export interface TransactionReconciliationStatementResponse {
|
||||
readonly closingBalance: number;
|
||||
}
|
||||
|
||||
export interface TransactionReconciliationStatementResponseItemWithInfo extends TransactionReconciliationStatementResponseItem {
|
||||
readonly sourceAccount?: Account;
|
||||
readonly sourceAccountName: string;
|
||||
readonly destinationAccount?: Account;
|
||||
readonly category?: TransactionCategory;
|
||||
readonly categoryName: string;
|
||||
}
|
||||
|
||||
export interface TransactionReconciliationStatementResponseWithInfo {
|
||||
readonly transactions: TransactionReconciliationStatementResponseItemWithInfo[];
|
||||
readonly totalInflows: number;
|
||||
readonly totalOutflows: number;
|
||||
readonly openingBalance: number;
|
||||
readonly closingBalance: number;
|
||||
}
|
||||
|
||||
export interface TransactionPageWrapper {
|
||||
readonly items: Transaction[];
|
||||
readonly totalCount?: number;
|
||||
|
||||
@@ -16,7 +16,8 @@ import type { Account } from '@/models/account.ts';
|
||||
import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||
import type {
|
||||
TransactionReconciliationStatementResponse,
|
||||
TransactionReconciliationStatementResponseItem
|
||||
TransactionReconciliationStatementResponseItemWithInfo,
|
||||
TransactionReconciliationStatementResponseWithInfo
|
||||
} from '@/models/transaction.ts';
|
||||
|
||||
import { replaceAll } from '@/lib/common.ts';
|
||||
@@ -48,7 +49,7 @@ export function useReconciliationStatementPageBase() {
|
||||
const accountId = ref<string>('');
|
||||
const startTime = ref<number>(0);
|
||||
const endTime = ref<number>(0);
|
||||
const reconciliationStatements = ref<TransactionReconciliationStatementResponse | undefined>(undefined);
|
||||
const reconciliationStatements = ref<TransactionReconciliationStatementResponseWithInfo | undefined>(undefined);
|
||||
|
||||
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
||||
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
|
||||
@@ -113,7 +114,34 @@ export function useReconciliationStatementPageBase() {
|
||||
}
|
||||
});
|
||||
|
||||
function getDisplayTransactionType(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function setReconciliationStatements(response: TransactionReconciliationStatementResponse | undefined) {
|
||||
if (!response) {
|
||||
reconciliationStatements.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const responseWithInfo: TransactionReconciliationStatementResponseWithInfo = {
|
||||
transactions: response.transactions.map(transaction => {
|
||||
const transactionWithInfo: TransactionReconciliationStatementResponseItemWithInfo = {
|
||||
...transaction,
|
||||
sourceAccount: allAccountsMap.value[transaction.sourceAccountId],
|
||||
sourceAccountName: allAccountsMap.value[transaction.sourceAccountId]?.name || '',
|
||||
destinationAccount: transaction.destinationAccountId && transaction.destinationAccountId !== '0' ? allAccountsMap.value[transaction.destinationAccountId] : undefined,
|
||||
category: allCategoriesMap.value[transaction.categoryId],
|
||||
categoryName: allCategoriesMap.value[transaction.categoryId]?.name || ''
|
||||
};
|
||||
return transactionWithInfo;
|
||||
}),
|
||||
totalInflows: response.totalInflows,
|
||||
totalOutflows: response.totalOutflows,
|
||||
openingBalance: response.openingBalance,
|
||||
closingBalance: response.closingBalance
|
||||
};
|
||||
|
||||
reconciliationStatements.value = responseWithInfo;
|
||||
}
|
||||
|
||||
function getDisplayTransactionType(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
if (transaction.type === TransactionType.ModifyBalance) {
|
||||
return tt('Modify Balance');
|
||||
} else if (transaction.type === TransactionType.Income) {
|
||||
@@ -131,54 +159,44 @@ export function useReconciliationStatementPageBase() {
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
return formatUnixTimeToLongDateTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value);
|
||||
}
|
||||
|
||||
function getDisplayDate(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getDisplayDate(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
return formatUnixTimeToLongDate(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value);
|
||||
}
|
||||
|
||||
function getDisplayTime(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getDisplayTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
return formatUnixTimeToShortTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value);
|
||||
}
|
||||
|
||||
function getDisplayTimezone(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getDisplayTimezone(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
return `UTC${getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)}`;
|
||||
}
|
||||
|
||||
function getDisplaySourceAmount(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
let currency = defaultCurrency.value;
|
||||
|
||||
if (allAccountsMap.value[transaction.sourceAccountId]) {
|
||||
currency = allAccountsMap.value[transaction.sourceAccountId]!.currency;
|
||||
}
|
||||
|
||||
function getDisplaySourceAmount(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
const currency = transaction.sourceAccount?.currency ?? defaultCurrency.value;
|
||||
return formatAmountToLocalizedNumeralsWithCurrency(transaction.sourceAmount, currency);
|
||||
}
|
||||
|
||||
function getDisplayDestinationAmount(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
let currency = defaultCurrency.value;
|
||||
|
||||
if (allAccountsMap.value[transaction.destinationAccountId]) {
|
||||
currency = allAccountsMap.value[transaction.destinationAccountId]!.currency;
|
||||
}
|
||||
|
||||
function getDisplayDestinationAmount(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
const currency = transaction.destinationAccount?.currency ?? defaultCurrency.value;
|
||||
return formatAmountToLocalizedNumeralsWithCurrency(transaction.destinationAmount, currency);
|
||||
}
|
||||
|
||||
function getDisplayAccountBalance(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getDisplayAccountBalance(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
let currency = defaultCurrency.value;
|
||||
let isLiabilityAccount = false;
|
||||
|
||||
if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) {
|
||||
if (allAccountsMap.value[transaction.destinationAccountId]) {
|
||||
currency = allAccountsMap.value[transaction.destinationAccountId]!.currency;
|
||||
isLiabilityAccount = allAccountsMap.value[transaction.destinationAccountId]!.isLiability;
|
||||
if (transaction.destinationAccount) {
|
||||
currency = transaction.destinationAccount.currency;
|
||||
isLiabilityAccount = transaction.destinationAccount.isLiability;
|
||||
}
|
||||
} else if (allAccountsMap.value[transaction.sourceAccountId]) {
|
||||
currency = allAccountsMap.value[transaction.sourceAccountId]!.currency;
|
||||
isLiabilityAccount = allAccountsMap.value[transaction.sourceAccountId]!.isLiability;
|
||||
} else if (transaction.sourceAccount) {
|
||||
currency = transaction.sourceAccount.currency;
|
||||
isLiabilityAccount = transaction.sourceAccount.isLiability;
|
||||
}
|
||||
|
||||
if (isLiabilityAccount) {
|
||||
@@ -211,9 +229,9 @@ export function useReconciliationStatementPageBase() {
|
||||
const rows = transactions.map(transaction => {
|
||||
const transactionTime = parseDateTimeFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value).getUnixTime();
|
||||
const type = getDisplayTransactionType(transaction);
|
||||
let categoryName = allCategoriesMap.value[transaction.categoryId]?.name || '';
|
||||
let categoryName = transaction.categoryName;
|
||||
let displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.sourceAmount);
|
||||
let displayAccountName = allAccountsMap.value[transaction.sourceAccountId]?.name || '';
|
||||
let displayAccountName = transaction.sourceAccountName;
|
||||
|
||||
if (transaction.type === TransactionType.ModifyBalance) {
|
||||
categoryName = tt('Modify Balance');
|
||||
@@ -221,8 +239,8 @@ export function useReconciliationStatementPageBase() {
|
||||
displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.destinationAmount);
|
||||
}
|
||||
|
||||
if (transaction.type === TransactionType.Transfer && allAccountsMap.value[transaction.destinationAccountId]) {
|
||||
displayAccountName = displayAccountName + ' → ' + (allAccountsMap.value[transaction.destinationAccountId]?.name || '');
|
||||
if (transaction.type === TransactionType.Transfer && transaction.destinationAccount) {
|
||||
displayAccountName = displayAccountName + ' → ' + (transaction.destinationAccount?.name || '');
|
||||
}
|
||||
|
||||
let displayAccountBalance = '';
|
||||
@@ -272,8 +290,6 @@ export function useReconciliationStatementPageBase() {
|
||||
currentAccountCurrency,
|
||||
isCurrentLiabilityAccount,
|
||||
exportFileName,
|
||||
allAccountsMap,
|
||||
allCategoriesMap,
|
||||
displayStartDateTime,
|
||||
displayEndDateTime,
|
||||
displayTotalInflows,
|
||||
@@ -282,6 +298,7 @@ export function useReconciliationStatementPageBase() {
|
||||
displayOpeningBalance,
|
||||
displayClosingBalance,
|
||||
// functions
|
||||
setReconciliationStatements,
|
||||
getDisplayTransactionType,
|
||||
getDisplayDateTime,
|
||||
getDisplayDate,
|
||||
|
||||
@@ -155,18 +155,18 @@
|
||||
:class="{ 'text-income' : item.type === TransactionType.Income, 'text-expense': item.type === TransactionType.Expense }"
|
||||
:color="getTransactionTypeColor(item)">{{ getDisplayTransactionType(item) }}</v-chip>
|
||||
</template>
|
||||
<template #item.categoryId="{ item }">
|
||||
<template #item.categoryName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<ItemIcon size="24px" icon-type="category"
|
||||
:icon-id="allCategoriesMap[item.categoryId]?.icon ?? ''"
|
||||
:color="allCategoriesMap[item.categoryId]?.color ?? ''"
|
||||
v-if="allCategoriesMap[item.categoryId] && allCategoriesMap[item.categoryId]?.color"></ItemIcon>
|
||||
<v-icon size="24" :icon="mdiPencilBoxOutline" v-else-if="!allCategoriesMap[item.categoryId] || !allCategoriesMap[item.categoryId]?.color" />
|
||||
:icon-id="item.category?.icon ?? ''"
|
||||
:color="item.category?.color ?? ''"
|
||||
v-if="item.category && item.category?.color"></ItemIcon>
|
||||
<v-icon size="24" :icon="mdiPencilBoxOutline" v-else-if="!item.category || !item.category?.color" />
|
||||
<span class="ms-2" v-if="item.type === TransactionType.ModifyBalance">
|
||||
{{ tt('Modify Balance') }}
|
||||
</span>
|
||||
<span class="ms-2" v-else-if="item.type !== TransactionType.ModifyBalance && allCategoriesMap[item.categoryId]">
|
||||
{{ allCategoriesMap[item.categoryId]?.name }}
|
||||
<span class="ms-2" v-else-if="item.type !== TransactionType.ModifyBalance && item.category">
|
||||
{{ item.category?.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -175,11 +175,11 @@
|
||||
<v-icon class="icon-with-direction mx-1" size="13" :icon="mdiArrowRight" v-if="item.type === TransactionType.Transfer && item.sourceAccountId !== item.destinationAccountId && getDisplaySourceAmount(item) !== getDisplayDestinationAmount(item)"></v-icon>
|
||||
<span v-if="item.type === TransactionType.Transfer && item.sourceAccountId !== item.destinationAccountId && getDisplaySourceAmount(item) !== getDisplayDestinationAmount(item)">{{ getDisplayDestinationAmount(item) }}</span>
|
||||
</template>
|
||||
<template #item.sourceAccountId="{ item }">
|
||||
<template #item.sourceAccountName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<span v-if="item.sourceAccountId && allAccountsMap[item.sourceAccountId]">{{ allAccountsMap[item.sourceAccountId]?.name }}</span>
|
||||
<span v-if="item.sourceAccount">{{ item.sourceAccount?.name }}</span>
|
||||
<v-icon class="icon-with-direction mx-1" size="13" :icon="mdiArrowRight" v-if="item.type === TransactionType.Transfer"></v-icon>
|
||||
<span v-if="item.type === TransactionType.Transfer && item.destinationAccountId && allAccountsMap[item.destinationAccountId]">{{ allAccountsMap[item.destinationAccountId]?.name }}</span>
|
||||
<span v-if="item.type === TransactionType.Transfer && item.destinationAccount">{{ item.destinationAccount?.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #item.accountBalance="{ item }">
|
||||
@@ -326,8 +326,6 @@ const {
|
||||
currentAccount,
|
||||
currentAccountCurrency,
|
||||
isCurrentLiabilityAccount,
|
||||
allAccountsMap,
|
||||
allCategoriesMap,
|
||||
exportFileName,
|
||||
displayStartDateTime,
|
||||
displayEndDateTime,
|
||||
@@ -336,6 +334,7 @@ const {
|
||||
displayTotalBalance,
|
||||
displayOpeningBalance,
|
||||
displayClosingBalance,
|
||||
setReconciliationStatements,
|
||||
getDisplayTransactionType,
|
||||
getDisplayDateTime,
|
||||
getDisplayTimezone,
|
||||
@@ -395,9 +394,9 @@ const dataTableHeaders = computed<object[]>(() => {
|
||||
|
||||
headers.push({ key: 'time', value: 'time', title: tt('Transaction Time'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'type', value: 'type', title: tt('Type'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'categoryId', value: 'categoryId', title: tt('Category'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'categoryName', value: 'categoryName', title: tt('Category'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'sourceAmount', value: 'sourceAmount', title: tt('Amount'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'sourceAccountId', value: 'sourceAccountId', title: tt('Account'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'sourceAccountName', value: 'sourceAccountName', title: tt('Account'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'accountBalance', value: 'accountBalance', title: tt(accountBalanceName), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'comment', value: 'comment', title: tt('Description'), sortable: true, nowrap: true });
|
||||
headers.push({ key: 'operation', title: tt('Operation'), sortable: false, nowrap: true, align: 'end' });
|
||||
@@ -464,7 +463,7 @@ function open(options: { accountId: string, startTime: number, endTime: number }
|
||||
endTime: options.endTime
|
||||
});
|
||||
}).then(result => {
|
||||
reconciliationStatements.value = result;
|
||||
setReconciliationStatements(result);
|
||||
loading.value = false;
|
||||
}).catch(error => {
|
||||
loading.value = false;
|
||||
@@ -496,7 +495,7 @@ function reload(force: boolean): void {
|
||||
}
|
||||
}
|
||||
|
||||
reconciliationStatements.value = result;
|
||||
setReconciliationStatements(result);
|
||||
loading.value = false;
|
||||
}).catch(error => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -188,10 +188,10 @@
|
||||
<div class="item-media">
|
||||
<div class="transaction-icon display-flex align-items-center">
|
||||
<ItemIcon icon-type="category"
|
||||
:icon-id="allCategoriesMap[item.transaction.categoryId]?.icon"
|
||||
:color="allCategoriesMap[item.transaction.categoryId]?.color"
|
||||
v-if="allCategoriesMap[item.transaction.categoryId] && allCategoriesMap[item.transaction.categoryId]?.color"></ItemIcon>
|
||||
<f7-icon v-else-if="!allCategoriesMap[item.transaction.categoryId] || !allCategoriesMap[item.transaction.categoryId]?.color"
|
||||
:icon-id="item.transaction.category?.icon"
|
||||
:color="item.transaction.category?.color"
|
||||
v-if="item.transaction.category && item.transaction.category?.color"></ItemIcon>
|
||||
<f7-icon v-else-if="!item.transaction.category || !item.transaction.category?.color"
|
||||
f7="pencil_ellipsis_rectangle">
|
||||
</f7-icon>
|
||||
</div>
|
||||
@@ -203,8 +203,8 @@
|
||||
<span v-if="item.transaction.type === TransactionType.ModifyBalance">
|
||||
{{ tt('Modify Balance') }}
|
||||
</span>
|
||||
<span v-else-if="item.transaction.type !== TransactionType.ModifyBalance && allCategoriesMap[item.transaction.categoryId]">
|
||||
{{ allCategoriesMap[item.transaction.categoryId]!.name }}
|
||||
<span v-else-if="item.transaction.type !== TransactionType.ModifyBalance && item.transaction.category">
|
||||
{{ item.transaction.category.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -352,7 +352,7 @@ import { AccountType } from '@/core/account.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts';
|
||||
import { type TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
|
||||
import { type TransactionReconciliationStatementResponseItemWithInfo } from '@/models/transaction.ts';
|
||||
|
||||
import { isDefined, isEquals, findDisplayNameByType } from '@/lib/common.ts';
|
||||
import {
|
||||
@@ -372,7 +372,7 @@ interface ReconciliationStatementVirtualListItem {
|
||||
index: number;
|
||||
type: ReconciliationStatementVirtualListItemType;
|
||||
displayDate?: string;
|
||||
transaction?: TransactionReconciliationStatementResponseItem;
|
||||
transaction?: TransactionReconciliationStatementResponseItemWithInfo;
|
||||
}
|
||||
|
||||
type ReconciliationStatementVirtualListItemType = 'transaction' | 'date';
|
||||
@@ -402,7 +402,6 @@ const {
|
||||
allDateAggregationTypes,
|
||||
currentTimezoneOffsetMinutes,
|
||||
isCurrentLiabilityAccount,
|
||||
allCategoriesMap,
|
||||
currentAccount,
|
||||
currentAccountCurrency,
|
||||
displayStartDateTime,
|
||||
@@ -412,6 +411,7 @@ const {
|
||||
displayTotalBalance,
|
||||
displayOpeningBalance,
|
||||
displayClosingBalance,
|
||||
setReconciliationStatements,
|
||||
getDisplayDate,
|
||||
getDisplayTime,
|
||||
getDisplayTimezone,
|
||||
@@ -430,7 +430,7 @@ const loadingError = ref<unknown | null>(null);
|
||||
const queryDateRangeType = ref<number>(DateRange.ThisMonth.type);
|
||||
const showAccountBalanceTrendsCharts = ref<boolean>(false);
|
||||
const chartDataDateAggregationType = ref<number>(ChartDateAggregationType.Day.type);
|
||||
const transactionToDelete = ref<TransactionReconciliationStatementResponseItem | null>(null);
|
||||
const transactionToDelete = ref<TransactionReconciliationStatementResponseItemWithInfo | null>(null);
|
||||
const newClosingBalance = ref<number>(0);
|
||||
const showDisplayModePopover = ref<boolean>(false);
|
||||
const showCustomDateRangeSheet = ref<boolean>(false);
|
||||
@@ -485,7 +485,7 @@ const chartDataDateAggregationTypeDisplayName = computed<string>(() => {
|
||||
return findDisplayNameByType(allDateAggregationTypes.value, chartDataDateAggregationType.value) || tt('Unknown');
|
||||
});
|
||||
|
||||
function getTransactionDomId(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
function getTransactionDomId(transaction: TransactionReconciliationStatementResponseItemWithInfo): string {
|
||||
return 'transaction_' + transaction.id;
|
||||
}
|
||||
|
||||
@@ -567,7 +567,7 @@ function reload(force: boolean): void {
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
reconciliationStatements.value = result;
|
||||
setReconciliationStatements(result);
|
||||
}).catch(error => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -581,11 +581,11 @@ function addTransaction(): void {
|
||||
props.f7router.navigate(`/transaction/add?accountId=${accountId.value}`);
|
||||
}
|
||||
|
||||
function duplicateTransaction(transaction: TransactionReconciliationStatementResponseItem): void {
|
||||
function duplicateTransaction(transaction: TransactionReconciliationStatementResponseItemWithInfo): void {
|
||||
props.f7router.navigate(`/transaction/add?id=${transaction.id}&type=${transaction.type}`);
|
||||
}
|
||||
|
||||
function editTransaction(transaction: TransactionReconciliationStatementResponseItem): void {
|
||||
function editTransaction(transaction: TransactionReconciliationStatementResponseItemWithInfo): void {
|
||||
props.f7router.navigate(`/transaction/edit?id=${transaction.id}&type=${transaction.type}`);
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ function updateClosingBalance(balance?: number): void {
|
||||
props.f7router.navigate(`/transaction/add?${params.join('&')}`);
|
||||
}
|
||||
|
||||
function removeTransaction(transaction: TransactionReconciliationStatementResponseItem | null, confirm: boolean): void {
|
||||
function removeTransaction(transaction: TransactionReconciliationStatementResponseItemWithInfo | null, confirm: boolean): void {
|
||||
if (!transaction) {
|
||||
showAlert('An error occurred');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user