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:
parent
36e0f8b0f7
commit
180933948f
@ -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"]');
|
||||
|
||||
@ -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}")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user