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>
|
</button>
|
||||||
</div>
|
</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 id="billingMatrixContainer" style="display: none;">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-hover mb-0" id="billingMatrixTable">
|
<table class="table table-sm table-hover mb-0" id="billingMatrixTable">
|
||||||
@ -2304,6 +2317,18 @@ function renderBillingMatrix(matrix) {
|
|||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).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
|
// Show matrix, hide loading
|
||||||
document.getElementById('billingMatrixContainer').style.display = 'block';
|
document.getElementById('billingMatrixContainer').style.display = 'block';
|
||||||
document.getElementById('billingMatrixLoading').style.display = 'none';
|
document.getElementById('billingMatrixLoading').style.display = 'none';
|
||||||
@ -2329,6 +2354,55 @@ function formatDKK(amount) {
|
|||||||
return amount.toLocaleString('da-DK', { style: 'currency', currency: 'DKK', minimumFractionDigits: 0 });
|
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
|
// Auto-load matrix when subscriptions tab is shown
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const subscriptionsTab = document.querySelector('a[href="#subscriptions"]');
|
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 logging
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from typing import Dict, Optional, List
|
from typing import Dict, Optional, List
|
||||||
from app.core.config import settings
|
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]:
|
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:
|
Args:
|
||||||
customer_number: e-conomic customer number
|
customer_number: e-conomic customer number
|
||||||
include_lines: Whether to include invoice lines (adds more API calls but gets full data)
|
include_lines: Whether to include invoice lines (adds more API calls but gets full data)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of invoice records with lines
|
List of invoice records with lines from the last year
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
async with aiohttp.ClientSession() as session:
|
||||||
# Try multiple endpoints to find invoices
|
# Try multiple endpoints to find invoices
|
||||||
# Include drafts, paid, unpaid, booked, sent, archived, etc.
|
# Include drafts, paid, unpaid, booked, sent, archived, etc.
|
||||||
@ -475,7 +480,11 @@ class EconomicService:
|
|||||||
while True:
|
while True:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
endpoint,
|
endpoint,
|
||||||
params={"pagesize": 1000, "skippages": page},
|
params={
|
||||||
|
"pagesize": 1000,
|
||||||
|
"skippages": page,
|
||||||
|
"filter": f"date$gte:{one_year_ago}"
|
||||||
|
},
|
||||||
headers=self._get_headers()
|
headers=self._get_headers()
|
||||||
) as response:
|
) as response:
|
||||||
logger.info(f"🔍 [API] Response status from {endpoint} (page {page}): {response.status}")
|
logger.info(f"🔍 [API] Response status from {endpoint} (page {page}): {response.status}")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user