Compare commits
No commits in common. "3a19f8233ed70da2c34a4af0b5722cca5dab366d" and "8ec12819f7fb620430b08c1ea6425759bd3c4815" have entirely different histories.
3a19f8233e
...
8ec12819f7
@ -178,7 +178,6 @@ class SubscriptionMatrixService:
|
|||||||
if other_ref:
|
if other_ref:
|
||||||
invoice_text_parts.append(str(other_ref))
|
invoice_text_parts.append(str(other_ref))
|
||||||
invoice_text = " ".join(invoice_text_parts).lower()
|
invoice_text = " ".join(invoice_text_parts).lower()
|
||||||
invoice_period = self._extract_period_from_text(invoice_text)
|
|
||||||
|
|
||||||
# Process invoice lines
|
# Process invoice lines
|
||||||
lines = invoice.get('lines', [])
|
lines = invoice.get('lines', [])
|
||||||
@ -195,14 +194,13 @@ class SubscriptionMatrixService:
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not product_number and not product_name:
|
if not product_number:
|
||||||
logger.debug(f"Skipping line without product number: {line}")
|
logger.debug(f"Skipping line without product number: {line}")
|
||||||
continue
|
continue
|
||||||
product_key = product_number or (product_name or "").strip().lower()
|
|
||||||
|
|
||||||
# Cache product name
|
# Cache product name
|
||||||
if product_key not in product_names:
|
if product_number not in product_names:
|
||||||
product_names[product_key] = product_name or f"Product {product_number}"
|
product_names[product_number] = product_name or f"Product {product_number}"
|
||||||
|
|
||||||
# Extract period from line
|
# Extract period from line
|
||||||
period_from_str = line.get('period', {}).get('from')
|
period_from_str = line.get('period', {}).get('from')
|
||||||
@ -210,12 +208,7 @@ class SubscriptionMatrixService:
|
|||||||
|
|
||||||
# If no period on line, use invoice date as month
|
# If no period on line, use invoice date as month
|
||||||
if not period_from_str:
|
if not period_from_str:
|
||||||
if invoice_period:
|
if invoice_date:
|
||||||
period_from = invoice_period
|
|
||||||
period_to = self._end_of_month(period_from)
|
|
||||||
period_from_str = period_from.isoformat().split('T')[0]
|
|
||||||
period_to_str = period_to.isoformat().split('T')[0]
|
|
||||||
elif invoice_date:
|
|
||||||
period_from_str = invoice_date
|
period_from_str = invoice_date
|
||||||
# Assume monthly billing if no end date
|
# Assume monthly billing if no end date
|
||||||
period_from = datetime.fromisoformat(invoice_date.replace('Z', '+00:00'))
|
period_from = datetime.fromisoformat(invoice_date.replace('Z', '+00:00'))
|
||||||
@ -253,9 +246,9 @@ class SubscriptionMatrixService:
|
|||||||
period_label = self._format_period_label(period_from, period_to)
|
period_label = self._format_period_label(period_from, period_to)
|
||||||
|
|
||||||
# Initialize product if first time within range
|
# Initialize product if first time within range
|
||||||
if product_key not in product_matrix:
|
if product_number not in product_matrix:
|
||||||
for month_key in all_months:
|
for month_key in all_months:
|
||||||
product_matrix[product_key][month_key] = {
|
product_matrix[product_number][month_key] = {
|
||||||
"amount": 0.0,
|
"amount": 0.0,
|
||||||
"status": "missing",
|
"status": "missing",
|
||||||
"invoice_number": None,
|
"invoice_number": None,
|
||||||
@ -265,9 +258,9 @@ class SubscriptionMatrixService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Update cell
|
# Update cell
|
||||||
cell = product_matrix[product_key][year_month]
|
cell = product_matrix[product_number][year_month]
|
||||||
cell["amount"] += amount
|
cell["amount"] = amount # Take last amount (or sum if multiple?)
|
||||||
cell["status"] = self._merge_status(cell["status"], self._determine_status(invoice_status))
|
cell["status"] = self._determine_status(invoice_status)
|
||||||
cell["invoice_number"] = invoice_number
|
cell["invoice_number"] = invoice_number
|
||||||
cell["period_from"] = period_from.isoformat().split('T')[0]
|
cell["period_from"] = period_from.isoformat().split('T')[0]
|
||||||
cell["period_to"] = period_to.isoformat().split('T')[0]
|
cell["period_to"] = period_to.isoformat().split('T')[0]
|
||||||
@ -337,54 +330,6 @@ class SubscriptionMatrixService:
|
|||||||
|
|
||||||
# Return sorted chronologically (oldest first)
|
# Return sorted chronologically (oldest first)
|
||||||
return sorted(months)
|
return sorted(months)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _end_of_month(dt: datetime) -> datetime:
|
|
||||||
"""Return last day of the month for a given date."""
|
|
||||||
next_month = dt.replace(day=28) + timedelta(days=4)
|
|
||||||
return next_month - timedelta(days=next_month.day)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_period_from_text(text: str) -> Optional[datetime]:
|
|
||||||
"""Extract month/year from invoice title/notes text (e.g., 'Periode May 2025')."""
|
|
||||||
if not text:
|
|
||||||
return None
|
|
||||||
month_map = {
|
|
||||||
"january": 1, "jan": 1, "januar": 1,
|
|
||||||
"february": 2, "feb": 2, "februar": 2,
|
|
||||||
"march": 3, "mar": 3, "marts": 3,
|
|
||||||
"april": 4, "apr": 4,
|
|
||||||
"may": 5, "maj": 5,
|
|
||||||
"june": 6, "jun": 6, "juni": 6,
|
|
||||||
"july": 7, "jul": 7, "juli": 7,
|
|
||||||
"august": 8, "aug": 8,
|
|
||||||
"september": 9, "sep": 9, "sept": 9,
|
|
||||||
"october": 10, "oct": 10, "okt": 10, "oktober": 10,
|
|
||||||
"november": 11, "nov": 11,
|
|
||||||
"december": 12, "dec": 12
|
|
||||||
}
|
|
||||||
tokens = text.replace(".", " ").replace("/", " ").split()
|
|
||||||
for i, token in enumerate(tokens[:-1]):
|
|
||||||
month = month_map.get(token.lower())
|
|
||||||
if month:
|
|
||||||
year_token = tokens[i + 1]
|
|
||||||
if year_token.isdigit() and len(year_token) == 4:
|
|
||||||
return datetime(int(year_token), month, 1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _merge_status(existing: str, incoming: str) -> str:
|
|
||||||
"""Keep the most 'complete' status when multiple invoices hit same cell."""
|
|
||||||
priority = {
|
|
||||||
"missing": 0,
|
|
||||||
"draft": 1,
|
|
||||||
"invoiced": 2,
|
|
||||||
"paid": 3,
|
|
||||||
"credited": 2
|
|
||||||
}
|
|
||||||
existing_key = existing or "missing"
|
|
||||||
incoming_key = incoming or "missing"
|
|
||||||
return incoming_key if priority.get(incoming_key, 0) >= priority.get(existing_key, 0) else existing_key
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_period_label(period_from: datetime, period_to: datetime) -> str:
|
def _format_period_label(period_from: datetime, period_to: datetime) -> str:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user