Adds custom date range filtering to transactions
Enables users to filter transactions by a custom date range, providing more flexibility in viewing transaction history. Prioritizes custom date range over preset days for filtering. Displays a warning if a user attempts to apply a custom date range without selecting both start and end dates.
This commit is contained in:
parent
f2df2f543b
commit
1d2eb05c36
4 changed files with 162 additions and 23 deletions
|
|
@ -855,13 +855,25 @@ class FavaClient:
|
|||
logger.error(f"Failed to fetch accounts via BQL: {e}")
|
||||
raise
|
||||
|
||||
async def get_journal_entries(self, days: int = None) -> List[Dict[str, Any]]:
|
||||
async def get_journal_entries(
|
||||
self,
|
||||
days: int = None,
|
||||
start_date: str = None,
|
||||
end_date: str = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get journal entries from Fava (with entry hashes), optionally filtered by date.
|
||||
|
||||
Args:
|
||||
days: If provided, only return entries from the last N days.
|
||||
If None, returns all entries (default behavior).
|
||||
start_date: ISO format date string (YYYY-MM-DD). If provided with end_date,
|
||||
filters entries between start_date and end_date (inclusive).
|
||||
end_date: ISO format date string (YYYY-MM-DD). If provided with start_date,
|
||||
filters entries between start_date and end_date (inclusive).
|
||||
|
||||
Note:
|
||||
If both days and start_date/end_date are provided, start_date/end_date takes precedence.
|
||||
|
||||
Returns:
|
||||
List of entries (transactions, opens, closes, etc.) with entry_hash field.
|
||||
|
|
@ -872,6 +884,9 @@ class FavaClient:
|
|||
|
||||
# Get only last 30 days
|
||||
recent = await fava.get_journal_entries(days=30)
|
||||
|
||||
# Get entries in custom date range
|
||||
custom = await fava.get_journal_entries(start_date="2024-01-01", end_date="2024-01-31")
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
|
|
@ -881,9 +896,33 @@ class FavaClient:
|
|||
entries = result.get("data", [])
|
||||
logger.info(f"Fava /journal returned {len(entries)} entries")
|
||||
|
||||
# Filter by date if requested
|
||||
if days is not None:
|
||||
# Filter by date range or days
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Use date range if both start_date and end_date are provided
|
||||
if start_date and end_date:
|
||||
try:
|
||||
filter_start = datetime.strptime(start_date, "%Y-%m-%d").date()
|
||||
filter_end = datetime.strptime(end_date, "%Y-%m-%d").date()
|
||||
filtered_entries = []
|
||||
for e in entries:
|
||||
entry_date_str = e.get("date")
|
||||
if entry_date_str:
|
||||
try:
|
||||
entry_date = datetime.strptime(entry_date_str, "%Y-%m-%d").date()
|
||||
if filter_start <= entry_date <= filter_end:
|
||||
filtered_entries.append(e)
|
||||
except (ValueError, TypeError):
|
||||
# Include entries with invalid dates (shouldn't happen)
|
||||
filtered_entries.append(e)
|
||||
logger.info(f"Filtered to {len(filtered_entries)} entries between {start_date} and {end_date}")
|
||||
entries = filtered_entries
|
||||
except ValueError as e:
|
||||
logger.error(f"Invalid date format: {e}")
|
||||
# Return all entries if date parsing fails
|
||||
|
||||
# Fall back to days filter if no date range provided
|
||||
elif days is not None:
|
||||
cutoff_date = (datetime.now() - timedelta(days=days)).date()
|
||||
filtered_entries = []
|
||||
for e in entries:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ window.app = Vue.createApp({
|
|||
transactionFilter: {
|
||||
user_id: null, // For filtering by user
|
||||
account_type: null, // For filtering by receivable/payable (asset/liability)
|
||||
days: 5 // Number of days to fetch (5, 30, 60, 90)
|
||||
dateRangeType: 15, // Preset days (15, 30, 60) or 'custom'
|
||||
startDate: null, // For custom date range (YYYY-MM-DD)
|
||||
endDate: null // For custom date range (YYYY-MM-DD)
|
||||
},
|
||||
accounts: [],
|
||||
currencies: [],
|
||||
|
|
@ -362,9 +364,16 @@ window.app = Vue.createApp({
|
|||
// Build query params with filters
|
||||
let queryParams = `limit=${limit}&offset=${currentOffset}`
|
||||
|
||||
// Add days filter (default 5)
|
||||
const days = this.transactionFilter.days || 5
|
||||
// Add date filter - custom range takes precedence over preset days
|
||||
if (this.transactionFilter.dateRangeType === 'custom' && this.transactionFilter.startDate && this.transactionFilter.endDate) {
|
||||
// Dates are already in YYYY-MM-DD format from q-date with mask
|
||||
queryParams += `&start_date=${this.transactionFilter.startDate}`
|
||||
queryParams += `&end_date=${this.transactionFilter.endDate}`
|
||||
} else {
|
||||
// Use preset days filter
|
||||
const days = typeof this.transactionFilter.dateRangeType === 'number' ? this.transactionFilter.dateRangeType : 15
|
||||
queryParams += `&days=${days}`
|
||||
}
|
||||
|
||||
if (this.transactionFilter.user_id) {
|
||||
queryParams += `&filter_user_id=${this.transactionFilter.user_id}`
|
||||
|
|
@ -403,11 +412,30 @@ window.app = Vue.createApp({
|
|||
this.transactionPagination.offset = 0
|
||||
this.loadTransactions(0)
|
||||
},
|
||||
setTransactionDays(days) {
|
||||
// Update days filter and reload from first page
|
||||
this.transactionFilter.days = days
|
||||
onDateRangeTypeChange(value) {
|
||||
// Handle date range type change (preset days or custom)
|
||||
if (value !== 'custom') {
|
||||
// Clear custom date range when switching to preset days
|
||||
this.transactionFilter.startDate = null
|
||||
this.transactionFilter.endDate = null
|
||||
// Load transactions with preset days
|
||||
this.transactionPagination.offset = 0
|
||||
this.loadTransactions(0)
|
||||
}
|
||||
// If switching to custom, don't load until user provides dates
|
||||
},
|
||||
applyCustomDateRange() {
|
||||
// Apply custom date range filter
|
||||
if (this.transactionFilter.startDate && this.transactionFilter.endDate) {
|
||||
this.transactionPagination.offset = 0
|
||||
this.loadTransactions(0)
|
||||
} else {
|
||||
this.$q.notify({
|
||||
type: 'warning',
|
||||
message: 'Please select both start and end dates',
|
||||
timeout: 3000
|
||||
})
|
||||
}
|
||||
},
|
||||
nextTransactionsPage() {
|
||||
if (this.transactionPagination.has_next) {
|
||||
|
|
|
|||
|
|
@ -337,23 +337,84 @@
|
|||
</div>
|
||||
|
||||
<!-- Date Range Selector -->
|
||||
<div class="row q-mb-md">
|
||||
<div class="row q-mb-md q-gutter-md">
|
||||
<div class="col-auto">
|
||||
<div class="text-caption text-grey q-mb-xs">Show transactions from:</div>
|
||||
<q-btn-toggle
|
||||
v-model="transactionFilter.days"
|
||||
v-model="transactionFilter.dateRangeType"
|
||||
toggle-color="primary"
|
||||
:options="[
|
||||
{label: 'Last 5 days', value: 5},
|
||||
{label: 'Last 15 days', value: 15},
|
||||
{label: 'Last 30 days', value: 30},
|
||||
{label: 'Last 60 days', value: 60},
|
||||
{label: 'Last 90 days', value: 90}
|
||||
{label: 'Custom Range', value: 'custom'}
|
||||
]"
|
||||
@update:model-value="setTransactionDays"
|
||||
@update:model-value="onDateRangeTypeChange"
|
||||
dense
|
||||
unelevated
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Custom Date Range Inputs -->
|
||||
<div v-if="transactionFilter.dateRangeType === 'custom'" class="col-auto row q-gutter-sm items-end">
|
||||
<div class="col-auto">
|
||||
<div class="text-caption text-grey q-mb-xs">From:</div>
|
||||
<q-input
|
||||
v-model="transactionFilter.startDate"
|
||||
type="date"
|
||||
outlined
|
||||
dense
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-date
|
||||
v-model="transactionFilter.startDate"
|
||||
mask="YYYY-MM-DD"
|
||||
>
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-date>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="text-caption text-grey q-mb-xs">To:</div>
|
||||
<q-input
|
||||
v-model="transactionFilter.endDate"
|
||||
type="date"
|
||||
outlined
|
||||
dense
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-date
|
||||
v-model="transactionFilter.endDate"
|
||||
mask="YYYY-MM-DD"
|
||||
>
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-date>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
color="primary"
|
||||
label="Apply"
|
||||
@click="applyCustomDateRange"
|
||||
:disable="!transactionFilter.startDate || !transactionFilter.endDate"
|
||||
unelevated
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar (Super User Only) -->
|
||||
|
|
|
|||
21
views_api.py
21
views_api.py
|
|
@ -399,7 +399,9 @@ async def api_get_user_entries(
|
|||
offset: int = 0,
|
||||
filter_user_id: str = None,
|
||||
filter_account_type: str = None, # 'asset' for receivable, 'liability' for payable
|
||||
days: int = 5, # Default 5 days, options: 5, 30, 60, 90
|
||||
days: int = 15, # Default 15 days, options: 15, 30, 60
|
||||
start_date: str = None, # ISO format: YYYY-MM-DD
|
||||
end_date: str = None, # ISO format: YYYY-MM-DD
|
||||
) -> dict:
|
||||
"""
|
||||
Get journal entries that affect the current user's accounts from Fava/Beancount.
|
||||
|
|
@ -407,7 +409,12 @@ async def api_get_user_entries(
|
|||
Returns transactions in reverse chronological order with optional filtering.
|
||||
|
||||
Args:
|
||||
days: Number of days to fetch (default: 5, options: 5, 30, 60, 90)
|
||||
days: Number of days to fetch (default: 15, options: 15, 30, 60)
|
||||
start_date: Start date for custom range (YYYY-MM-DD). Requires end_date.
|
||||
end_date: End date for custom range (YYYY-MM-DD). Requires start_date.
|
||||
|
||||
Note:
|
||||
If both days and start_date/end_date are provided, start_date/end_date takes precedence.
|
||||
"""
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
from .fava_client import get_fava_client
|
||||
|
|
@ -422,9 +429,13 @@ async def api_get_user_entries(
|
|||
# Regular user can only see their own entries
|
||||
target_user_id = wallet.wallet.user
|
||||
|
||||
# Get journal entries from Fava (default last 5 days for performance)
|
||||
# User can request 30, 60, or 90 days via query parameter
|
||||
all_entries = await fava.get_journal_entries(days=days)
|
||||
# Get journal entries from Fava
|
||||
# Priority: custom date range > days > default (5 days)
|
||||
all_entries = await fava.get_journal_entries(
|
||||
days=days,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
|
||||
# Filter and transform entries
|
||||
filtered_entries = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue