217 lines
8.4 KiB
Python
217 lines
8.4 KiB
Python
"""
|
|
BMC Office Subscriptions Upload Router
|
|
Allows admins to upload Excel files with subscription data
|
|
"""
|
|
|
|
from fastapi import APIRouter, UploadFile, File, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
import pandas as pd
|
|
from datetime import datetime
|
|
import logging
|
|
from io import BytesIO
|
|
|
|
from app.core.database import execute_query, execute_update
|
|
from app.services.customer_activity_logger import CustomerActivityLogger
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def parse_danish_number(value):
|
|
"""Convert Danish number format to float (2.995,00 -> 2995.00)"""
|
|
if pd.isna(value) or value == '':
|
|
return 0.0
|
|
if isinstance(value, (int, float)):
|
|
return float(value)
|
|
|
|
value_str = str(value).strip()
|
|
value_str = value_str.replace('.', '').replace(',', '.')
|
|
try:
|
|
return float(value_str)
|
|
except:
|
|
return 0.0
|
|
|
|
|
|
def parse_danish_date(value):
|
|
"""Convert DD.MM.YYYY to YYYY-MM-DD"""
|
|
if pd.isna(value) or value == '':
|
|
return None
|
|
|
|
if isinstance(value, datetime):
|
|
return value.strftime('%Y-%m-%d')
|
|
|
|
value_str = str(value).strip()
|
|
try:
|
|
dt = datetime.strptime(value_str, '%d.%m.%Y')
|
|
return dt.strftime('%Y-%m-%d')
|
|
except:
|
|
try:
|
|
dt = pd.to_datetime(value_str)
|
|
return dt.strftime('%Y-%m-%d')
|
|
except:
|
|
return None
|
|
|
|
|
|
@router.post("/admin/bmc-office-subscriptions/upload")
|
|
async def upload_bmc_office_subscriptions(file: UploadFile = File(...)):
|
|
"""
|
|
Upload Excel file with BMC Office subscriptions
|
|
Expected columns: FirmaID, Firma, Startdate, Text, Antal, Pris, Rabat, Beskrivelse, FakturaFirmaID, FakturaFirma
|
|
"""
|
|
|
|
if not file.filename.endswith(('.xlsx', '.xls')):
|
|
raise HTTPException(status_code=400, detail="Kun Excel filer (.xlsx, .xls) er tilladt")
|
|
|
|
try:
|
|
# Read Excel file
|
|
contents = await file.read()
|
|
df = pd.read_excel(BytesIO(contents))
|
|
|
|
logger.info(f"📂 Læst Excel fil: {file.filename} med {len(df)} rækker")
|
|
logger.info(f"📋 Kolonner: {', '.join(df.columns)}")
|
|
|
|
# Get all customers from database
|
|
customers = execute_query("SELECT id, name FROM customers ORDER BY name")
|
|
customer_map = {c['name'].lower().strip(): c['id'] for c in customers}
|
|
|
|
logger.info(f"✅ Fundet {len(customers)} kunder i databasen")
|
|
|
|
# Get current statistics before deletion
|
|
old_stats = execute_query("""
|
|
SELECT
|
|
COUNT(*) as total_records,
|
|
ROUND(SUM(CASE WHEN active THEN total_inkl_moms ELSE 0 END)::numeric, 2) as total_value_dkk
|
|
FROM bmc_office_subscription_totals
|
|
""")[0]
|
|
|
|
old_total = old_stats['total_records']
|
|
old_value = float(old_stats['total_value_dkk'] or 0)
|
|
|
|
# Get affected customers before deletion (for activity log)
|
|
affected_customers = execute_query("""
|
|
SELECT DISTINCT customer_id, COUNT(*) as count,
|
|
ROUND(SUM(total_inkl_moms)::numeric, 2) as value
|
|
FROM bmc_office_subscription_totals
|
|
WHERE active = true
|
|
GROUP BY customer_id
|
|
""")
|
|
|
|
# Clear existing subscriptions
|
|
execute_update("DELETE FROM bmc_office_subscriptions", ())
|
|
logger.info(f"🗑️ Ryddet {old_total} eksisterende abonnementer (værdi: {old_value:,.2f} DKK)")
|
|
|
|
# Log deletion for each affected customer
|
|
for customer in affected_customers:
|
|
CustomerActivityLogger.log(
|
|
customer_id=customer['customer_id'],
|
|
activity_type='bmc_office_sync',
|
|
description=f"BMC Office abonnementer fjernet før ny import: {customer['count']} abonnementer (værdi: {float(customer['value']):,.2f} DKK)"
|
|
)
|
|
|
|
# Process rows
|
|
imported = 0
|
|
skipped = 0
|
|
errors = []
|
|
|
|
for idx, row in df.iterrows():
|
|
try:
|
|
firma_name = str(row.get('Firma', '')).strip()
|
|
if not firma_name:
|
|
skipped += 1
|
|
continue
|
|
|
|
# Find customer by name (case-insensitive)
|
|
customer_id = None
|
|
firma_lower = firma_name.lower()
|
|
|
|
# Exact match first
|
|
if firma_lower in customer_map:
|
|
customer_id = customer_map[firma_lower]
|
|
else:
|
|
# Partial match
|
|
for cust_name, cust_id in customer_map.items():
|
|
if firma_lower in cust_name or cust_name in firma_lower:
|
|
customer_id = cust_id
|
|
break
|
|
|
|
if not customer_id:
|
|
skipped += 1
|
|
errors.append(f"Række {idx+2}: Kunde '{firma_name}' ikke fundet")
|
|
continue
|
|
|
|
# Parse data
|
|
firma_id = str(row.get('FirmaID', '')).strip()
|
|
start_date = parse_danish_date(row.get('Startdate'))
|
|
text = str(row.get('Text', '')).strip()
|
|
antal = parse_danish_number(row.get('Antal', 1))
|
|
pris = parse_danish_number(row.get('Pris', 0))
|
|
rabat = parse_danish_number(row.get('Rabat', 0))
|
|
beskrivelse = str(row.get('Beskrivelse', '')).strip()
|
|
faktura_firma_id = str(row.get('FakturaFirmaID', '')).strip()
|
|
faktura_firma_name = str(row.get('FakturaFirma', '')).strip()
|
|
|
|
# Insert subscription
|
|
execute_update(
|
|
"""INSERT INTO bmc_office_subscriptions
|
|
(customer_id, firma_id, firma_name, start_date, text, antal, pris, rabat,
|
|
beskrivelse, faktura_firma_id, faktura_firma_name, active)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
|
|
(customer_id, firma_id, firma_name, start_date, text, antal, pris, rabat,
|
|
beskrivelse, faktura_firma_id, faktura_firma_name, True)
|
|
)
|
|
|
|
imported += 1
|
|
|
|
except Exception as e:
|
|
skipped += 1
|
|
errors.append(f"Række {idx+2}: {str(e)}")
|
|
logger.error(f"❌ Fejl ved import af række {idx+2}: {e}")
|
|
|
|
# Get statistics
|
|
stats = execute_query("""
|
|
SELECT
|
|
COUNT(*) as total_records,
|
|
COUNT(CASE WHEN active THEN 1 END) as active_records,
|
|
ROUND(SUM(CASE WHEN active THEN total_inkl_moms ELSE 0 END)::numeric, 2) as total_value_dkk
|
|
FROM bmc_office_subscription_totals
|
|
""")[0]
|
|
|
|
# Get new subscriptions per customer for activity log
|
|
new_subscriptions = execute_query("""
|
|
SELECT customer_id, COUNT(*) as count,
|
|
ROUND(SUM(total_inkl_moms)::numeric, 2) as value,
|
|
string_agg(text, ', ' ORDER BY text) as products
|
|
FROM bmc_office_subscription_totals
|
|
WHERE active = true
|
|
GROUP BY customer_id
|
|
""")
|
|
|
|
# Log import for each customer
|
|
for customer in new_subscriptions:
|
|
CustomerActivityLogger.log(
|
|
customer_id=customer['customer_id'],
|
|
activity_type='bmc_office_sync',
|
|
description=f"BMC Office abonnementer importeret: {customer['count']} abonnementer (værdi: {float(customer['value']):,.2f} DKK) - Produkter: {customer['products'][:200]}"
|
|
)
|
|
|
|
logger.info(f"✅ Import færdig: {imported} importeret, {skipped} sprunget over")
|
|
|
|
return {
|
|
"success": True,
|
|
"filename": file.filename,
|
|
"total_rows": len(df),
|
|
"imported": imported,
|
|
"skipped": skipped,
|
|
"errors": errors[:10], # Max 10 error messages
|
|
"statistics": {
|
|
"total_records": stats['total_records'],
|
|
"active_records": stats['active_records'],
|
|
"total_value_dkk": float(stats['total_value_dkk'] or 0)
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ Fejl ved upload: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Fejl ved upload: {str(e)}")
|