From 39b49d4d543b2801f028ff4a813d88e0ea5e7c9f Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 27 Jan 2026 00:34:44 +0100 Subject: [PATCH] feat: Include invoice title keywords in subscription matrix filter - Match 'periode'/'abonnement' in invoice notes/title fields - Allows period-only titles to include line items - Keeps line-level keyword filtering --- app/services/subscription_matrix.py | 80 +++++++++++++++++------------ 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/app/services/subscription_matrix.py b/app/services/subscription_matrix.py index da8a8ae..ad73554 100644 --- a/app/services/subscription_matrix.py +++ b/app/services/subscription_matrix.py @@ -149,30 +149,9 @@ class SubscriptionMatrixService: product_matrix = defaultdict(dict) product_names = {} # Cache product names - # Initialize all products with empty cells for all months + # Initialize products only if they have data within the selected month range try: - for invoice in invoices: - lines = invoice.get('lines', []) - for line in lines: - product_number = line.get('product', {}).get('productNumber') - product_name = line.get('product', {}).get('name') or line.get('description') - - if product_number and product_number not in product_names: - product_names[product_number] = product_name or f"Product {product_number}" - - # Initialize all products with all months (empty) - for product_number in product_names.keys(): - for year_month in all_months: - product_matrix[product_number][year_month] = { - "amount": 0.0, - "status": "missing", - "invoice_number": None, - "period_from": None, - "period_to": None, - "period_label": None - } - - # Now fill in data from invoices + # Fill in data from invoices, but only for months within range for invoice in invoices: invoice_number = invoice.get('bookedInvoiceNumber') or invoice.get('draftInvoiceNumber') # Determine status based on invoice type/endpoint it came from @@ -185,12 +164,35 @@ class SubscriptionMatrixService: elif invoice.get('draftInvoiceNumber'): invoice_status = 'draft' invoice_date = invoice.get('date') + # Build invoice-level text (title/notes) for keyword matching + invoice_text_parts = [] + notes = invoice.get('notes') + if isinstance(notes, dict): + for key in ["heading", "textLine1", "textLine2", "textLine3", "textLine4", "textLine5"]: + val = notes.get(key) + if val: + invoice_text_parts.append(str(val)) + elif notes: + invoice_text_parts.append(str(notes)) + other_ref = invoice.get('otherReference') or invoice.get('orderNumberDb') + if other_ref: + invoice_text_parts.append(str(other_ref)) + invoice_text = " ".join(invoice_text_parts).lower() # Process invoice lines lines = invoice.get('lines', []) for line in lines: product_number = line.get('product', {}).get('productNumber') product_name = line.get('product', {}).get('name') or line.get('description') + line_description = (line.get('description') or product_name or "").lower() + # Only include lines that indicate a period or subscription + if ( + "periode" not in line_description + and "abonnement" not in line_description + and "periode" not in invoice_text + and "abonnement" not in invoice_text + ): + continue if not product_number: logger.debug(f"Skipping line without product number: {line}") @@ -236,19 +238,33 @@ class SubscriptionMatrixService: # Determine month key (use first month if multi-month) year_month = period_from.strftime('%Y-%m') + if year_month not in all_months: + # Skip lines outside the displayed month range + continue # Calculate period label period_label = self._format_period_label(period_from, period_to) - # Update cell (it should already exist from initialization) - if year_month in product_matrix.get(product_number, {}): - cell = product_matrix[product_number][year_month] - cell["amount"] = amount # Take last amount (or sum if multiple?) - cell["status"] = self._determine_status(invoice_status) - cell["invoice_number"] = invoice_number - cell["period_from"] = period_from.isoformat().split('T')[0] - cell["period_to"] = period_to.isoformat().split('T')[0] - cell["period_label"] = period_label + # Initialize product if first time within range + if product_number not in product_matrix: + for month_key in all_months: + product_matrix[product_number][month_key] = { + "amount": 0.0, + "status": "missing", + "invoice_number": None, + "period_from": None, + "period_to": None, + "period_label": None + } + + # Update cell + cell = product_matrix[product_number][year_month] + cell["amount"] = amount # Take last amount (or sum if multiple?) + cell["status"] = self._determine_status(invoice_status) + cell["invoice_number"] = invoice_number + cell["period_from"] = period_from.isoformat().split('T')[0] + cell["period_to"] = period_to.isoformat().split('T')[0] + cell["period_label"] = period_label except Exception as e: logger.error(f"❌ Error aggregating data: {e}")