feat: Add 1-year filter + search to subscription matrix

v1.3.135:
- Added date filter to e-conomic API (only fetch invoices from last year)
- Implemented product search in billing matrix
- Shows/hides search field based on product count
- Real-time filtering with clear button
This commit is contained in:
Christian 2026-01-27 07:08:09 +01:00
parent 36e0f8b0f7
commit 180933948f
3 changed files with 87 additions and 4 deletions

View File

@ -1 +1 @@
1.3.134
1.3.135

View File

@ -507,6 +507,19 @@
</button>
</div>
<!-- Search field -->
<div class="mb-3" id="matrixSearchContainer" style="display: none;">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-search"></i>
</span>
<input type="text" class="form-control" id="matrixSearchInput" placeholder="Søg efter produkt..." onkeyup="filterMatrixProducts()">
<button class="btn btn-outline-secondary" type="button" onclick="clearMatrixSearch()">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div id="billingMatrixContainer" style="display: none;">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0" id="billingMatrixTable">
@ -2304,6 +2317,18 @@ function renderBillingMatrix(matrix) {
</tr>`;
}).join('');
// Populate table body
const tableBody = document.getElementById('matrixBodyRows');
tableBody.innerHTML = matrixHtml;
// Show search container if there are products
const searchContainer = document.getElementById('matrixSearchContainer');
if (matrix.products.length > 0) {
searchContainer.style.display = 'block';
} else {
searchContainer.style.display = 'none';
}
// Show matrix, hide loading
document.getElementById('billingMatrixContainer').style.display = 'block';
document.getElementById('billingMatrixLoading').style.display = 'none';
@ -2329,6 +2354,55 @@ function formatDKK(amount) {
return amount.toLocaleString('da-DK', { style: 'currency', currency: 'DKK', minimumFractionDigits: 0 });
}
/**
* Filter products in billing matrix based on search input
*/
function filterMatrixProducts() {
const searchInput = document.getElementById('matrixSearchInput');
const searchTerm = searchInput.value.toLowerCase();
const tableBody = document.getElementById('matrixBodyRows');
const rows = tableBody.getElementsByTagName('tr');
let visibleCount = 0;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const productName = row.cells[0].textContent.toLowerCase();
if (productName.includes(searchTerm)) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
}
// Show message if no results
if (visibleCount === 0 && searchTerm.length > 0) {
if (!document.getElementById('matrixNoResults')) {
const noResultsRow = document.createElement('tr');
noResultsRow.id = 'matrixNoResults';
noResultsRow.innerHTML = '<td colspan="100" class="text-center text-muted py-3"><i class="bi bi-search me-2"></i>Ingen produkter matcher søgningen</td>';
tableBody.appendChild(noResultsRow);
}
} else {
const noResultsRow = document.getElementById('matrixNoResults');
if (noResultsRow) {
noResultsRow.remove();
}
}
}
/**
* Clear matrix search filter
*/
function clearMatrixSearch() {
const searchInput = document.getElementById('matrixSearchInput');
searchInput.value = '';
filterMatrixProducts();
searchInput.focus();
}
// Auto-load matrix when subscriptions tab is shown
document.addEventListener('DOMContentLoaded', () => {
const subscriptionsTab = document.querySelector('a[href="#subscriptions"]');

View File

@ -9,6 +9,7 @@ Send invoices and supplier invoices (kassekladde) to e-conomic accounting system
import logging
import aiohttp
import json
from datetime import datetime, timedelta
from typing import Dict, Optional, List
from app.core.config import settings
@ -439,16 +440,20 @@ class EconomicService:
async def get_customer_invoices(self, customer_number: int, include_lines: bool = True) -> List[Dict]:
"""
Get customer invoices (sales invoices) from e-conomic
Get customer invoices (sales invoices) from e-conomic (last 1 year only)
Args:
customer_number: e-conomic customer number
include_lines: Whether to include invoice lines (adds more API calls but gets full data)
Returns:
List of invoice records with lines
List of invoice records with lines from the last year
"""
try:
# Calculate date 1 year ago
one_year_ago = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d')
logger.info(f"📅 Fetching invoices from {one_year_ago} onwards (1 year filter)")
async with aiohttp.ClientSession() as session:
# Try multiple endpoints to find invoices
# Include drafts, paid, unpaid, booked, sent, archived, etc.
@ -475,7 +480,11 @@ class EconomicService:
while True:
async with session.get(
endpoint,
params={"pagesize": 1000, "skippages": page},
params={
"pagesize": 1000,
"skippages": page,
"filter": f"date$gte:{one_year_ago}"
},
headers=self._get_headers()
) as response:
logger.info(f"🔍 [API] Response status from {endpoint} (page {page}): {response.status}")