Compare commits
2 Commits
6de869c86a
...
8ec457bba1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ec457bba1 | ||
|
|
1b48e659a8 |
@ -140,13 +140,39 @@ class SubscriptionMatrixService:
|
|||||||
months: Number of months to include
|
months: Number of months to include
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of product records with aggregated monthly data
|
List of product records with aggregated monthly data (including empty months)
|
||||||
"""
|
"""
|
||||||
|
# Generate list of all months to display (last N months from today)
|
||||||
|
all_months = self._generate_month_range(months)
|
||||||
|
|
||||||
# Structure: {product_number: {year_month: {amount, status, invoice_number, period_from, period_to}}}
|
# Structure: {product_number: {year_month: {amount, status, invoice_number, period_from, period_to}}}
|
||||||
product_matrix = defaultdict(dict)
|
product_matrix = defaultdict(dict)
|
||||||
product_names = {} # Cache product names
|
product_names = {} # Cache product names
|
||||||
|
|
||||||
|
# Initialize all products with empty cells for all months
|
||||||
try:
|
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
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
invoice_number = invoice.get('bookedInvoiceNumber') or invoice.get('draftInvoiceNumber')
|
invoice_number = invoice.get('bookedInvoiceNumber') or invoice.get('draftInvoiceNumber')
|
||||||
# Determine status based on invoice type/endpoint it came from
|
# Determine status based on invoice type/endpoint it came from
|
||||||
@ -214,24 +240,15 @@ class SubscriptionMatrixService:
|
|||||||
# Calculate period label
|
# Calculate period label
|
||||||
period_label = self._format_period_label(period_from, period_to)
|
period_label = self._format_period_label(period_from, period_to)
|
||||||
|
|
||||||
# Initialize cell if doesn't exist
|
# Update cell (it should already exist from initialization)
|
||||||
if year_month not in product_matrix[product_number]:
|
if year_month in product_matrix.get(product_number, {}):
|
||||||
product_matrix[product_number][year_month] = {
|
cell = product_matrix[product_number][year_month]
|
||||||
"amount": 0.0,
|
cell["amount"] = amount # Take last amount (or sum if multiple?)
|
||||||
"status": "missing",
|
cell["status"] = self._determine_status(invoice_status)
|
||||||
"invoice_number": None,
|
cell["invoice_number"] = invoice_number
|
||||||
"period_from": None,
|
cell["period_from"] = period_from.isoformat().split('T')[0]
|
||||||
"period_to": None
|
cell["period_to"] = period_to.isoformat().split('T')[0]
|
||||||
}
|
cell["period_label"] = period_label
|
||||||
|
|
||||||
# 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:
|
except Exception as e:
|
||||||
logger.error(f"❌ Error aggregating data: {e}")
|
logger.error(f"❌ Error aggregating data: {e}")
|
||||||
@ -268,6 +285,36 @@ class SubscriptionMatrixService:
|
|||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_month_range(num_months: int) -> List[str]:
|
||||||
|
"""
|
||||||
|
Generate list of month keys for the last N months
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_months: Number of months to generate (e.g., 12)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Sorted list of 'YYYY-MM' strings going back from today
|
||||||
|
"""
|
||||||
|
months = []
|
||||||
|
today = datetime.now()
|
||||||
|
|
||||||
|
for i in range(num_months):
|
||||||
|
# Go back i months from today using calendar arithmetic
|
||||||
|
year = today.year
|
||||||
|
month = today.month - i
|
||||||
|
|
||||||
|
# Adjust year and month if month goes negative
|
||||||
|
while month <= 0:
|
||||||
|
month += 12
|
||||||
|
year -= 1
|
||||||
|
|
||||||
|
month_key = f"{year:04d}-{month:02d}"
|
||||||
|
months.append(month_key)
|
||||||
|
|
||||||
|
# Return sorted chronologically (oldest first)
|
||||||
|
return sorted(months)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_period_label(period_from: datetime, period_to: datetime) -> str:
|
def _format_period_label(period_from: datetime, period_to: datetime) -> str:
|
||||||
"""Format period as readable label (e.g., 'Jan', 'Jan-Mar', etc)"""
|
"""Format period as readable label (e.g., 'Jan', 'Jan-Mar', etc)"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user