diff --git a/.env.example b/.env.example index 2d7b0c2..d8025d1 100644 --- a/.env.example +++ b/.env.example @@ -70,6 +70,19 @@ ECONOMIC_AGREEMENT_GRANT_TOKEN=your_agreement_grant_token_here ECONOMIC_READ_ONLY=true # Set to false ONLY after testing ECONOMIC_DRY_RUN=true # Set to false ONLY when ready for production writes +# ===================================================== +# ALSO Cloud Marketplace Integration (Optional) +# ===================================================== +ALSO_ENABLED=false +ALSO_API_BASE_URL= +ALSO_API_KEY= +ALSO_API_SECRET= +ALSO_TIMEOUT_SECONDS=20 + +# 🚨 SAFETY SWITCHES - Beskytter mod utilsigtede importer/sync +ALSO_READ_ONLY=true +ALSO_DRY_RUN=true + # ===================================================== # FedEx Integration (Optional) # ===================================================== diff --git a/.env.prod.example b/.env.prod.example index 0ee8ddf..cf3454e 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -87,6 +87,20 @@ ECONOMIC_AGREEMENT_GRANT_TOKEN=your_production_grant_here ECONOMIC_READ_ONLY=true ECONOMIC_DRY_RUN=true +# ===================================================== +# ALSO Cloud Marketplace Integration - Production (Optional) +# ===================================================== +ALSO_ENABLED=false +ALSO_API_BASE_URL= +ALSO_API_KEY= +ALSO_API_SECRET= +ALSO_TIMEOUT_SECONDS=20 + +# 🚨 SAFETY SWITCHES +# Start ALTID med begge sat til true i ny production deployment! +ALSO_READ_ONLY=true +ALSO_DRY_RUN=true + # ===================================================== # FedEx Integration - Production # ===================================================== diff --git a/# GitHub Copilot Instructions - BMC Webs.prompt.md b/MDfile/# GitHub Copilot Instructions - BMC Webs.prompt.md similarity index 100% rename from # GitHub Copilot Instructions - BMC Webs.prompt.md rename to MDfile/# GitHub Copilot Instructions - BMC Webs.prompt.md diff --git a/BACKUP_RESTORE_TEST_PLAN.md b/MDfile/BACKUP_RESTORE_TEST_PLAN.md similarity index 100% rename from BACKUP_RESTORE_TEST_PLAN.md rename to MDfile/BACKUP_RESTORE_TEST_PLAN.md diff --git a/DEPLOYMENT_CHECKLIST.md b/MDfile/DEPLOYMENT_CHECKLIST.md similarity index 100% rename from DEPLOYMENT_CHECKLIST.md rename to MDfile/DEPLOYMENT_CHECKLIST.md diff --git a/DEVELOPMENT.md b/MDfile/DEVELOPMENT.md similarity index 100% rename from DEVELOPMENT.md rename to MDfile/DEVELOPMENT.md diff --git a/LOCATION_MODULE_IMPLEMENTATION_COMPLETE.md b/MDfile/LOCATION_MODULE_IMPLEMENTATION_COMPLETE.md similarity index 100% rename from LOCATION_MODULE_IMPLEMENTATION_COMPLETE.md rename to MDfile/LOCATION_MODULE_IMPLEMENTATION_COMPLETE.md diff --git a/MIGRATION_GUIDE_v2.0.0.md b/MDfile/MIGRATION_GUIDE_v2.0.0.md similarity index 100% rename from MIGRATION_GUIDE_v2.0.0.md rename to MDfile/MIGRATION_GUIDE_v2.0.0.md diff --git a/NEXTCLOUD_MODULE_PLAN.md b/MDfile/NEXTCLOUD_MODULE_PLAN.md similarity index 100% rename from NEXTCLOUD_MODULE_PLAN.md rename to MDfile/NEXTCLOUD_MODULE_PLAN.md diff --git a/PHASE_3_TASK_3_1_COMPLETE.md b/MDfile/PHASE_3_TASK_3_1_COMPLETE.md similarity index 100% rename from PHASE_3_TASK_3_1_COMPLETE.md rename to MDfile/PHASE_3_TASK_3_1_COMPLETE.md diff --git a/PRODUCTION_DEPLOYMENT.md b/MDfile/PRODUCTION_DEPLOYMENT.md similarity index 100% rename from PRODUCTION_DEPLOYMENT.md rename to MDfile/PRODUCTION_DEPLOYMENT.md diff --git a/PRODUCTION_QUICK_START.md b/MDfile/PRODUCTION_QUICK_START.md similarity index 100% rename from PRODUCTION_QUICK_START.md rename to MDfile/PRODUCTION_QUICK_START.md diff --git a/QUICK_UPDATE.md b/MDfile/QUICK_UPDATE.md similarity index 100% rename from QUICK_UPDATE.md rename to MDfile/QUICK_UPDATE.md diff --git a/README.md b/MDfile/README.md similarity index 100% rename from README.md rename to MDfile/README.md diff --git a/RELEASE_NOTES_v1.0.0.md b/MDfile/RELEASE_NOTES_v1.0.0.md similarity index 100% rename from RELEASE_NOTES_v1.0.0.md rename to MDfile/RELEASE_NOTES_v1.0.0.md diff --git a/RELEASE_NOTES_v1.3.5.md b/MDfile/RELEASE_NOTES_v1.3.5.md similarity index 100% rename from RELEASE_NOTES_v1.3.5.md rename to MDfile/RELEASE_NOTES_v1.3.5.md diff --git a/RELEASE_NOTES_v1.3.75.md b/MDfile/RELEASE_NOTES_v1.3.75.md similarity index 100% rename from RELEASE_NOTES_v1.3.75.md rename to MDfile/RELEASE_NOTES_v1.3.75.md diff --git a/RELEASE_NOTES_v1.3.76.md b/MDfile/RELEASE_NOTES_v1.3.76.md similarity index 100% rename from RELEASE_NOTES_v1.3.76.md rename to MDfile/RELEASE_NOTES_v1.3.76.md diff --git a/RELEASE_NOTES_v1.3.84.md b/MDfile/RELEASE_NOTES_v1.3.84.md similarity index 100% rename from RELEASE_NOTES_v1.3.84.md rename to MDfile/RELEASE_NOTES_v1.3.84.md diff --git a/RELEASE_NOTES_v2.0.0.md b/MDfile/RELEASE_NOTES_v2.0.0.md similarity index 100% rename from RELEASE_NOTES_v2.0.0.md rename to MDfile/RELEASE_NOTES_v2.0.0.md diff --git a/RELEASE_NOTES_v2.0.1.md b/MDfile/RELEASE_NOTES_v2.0.1.md similarity index 100% rename from RELEASE_NOTES_v2.0.1.md rename to MDfile/RELEASE_NOTES_v2.0.1.md diff --git a/RELEASE_NOTES_v2.0.2.md b/MDfile/RELEASE_NOTES_v2.0.2.md similarity index 100% rename from RELEASE_NOTES_v2.0.2.md rename to MDfile/RELEASE_NOTES_v2.0.2.md diff --git a/RELEASE_NOTES_v2.0.3.md b/MDfile/RELEASE_NOTES_v2.0.3.md similarity index 100% rename from RELEASE_NOTES_v2.0.3.md rename to MDfile/RELEASE_NOTES_v2.0.3.md diff --git a/RELEASE_NOTES_v2.0.4.md b/MDfile/RELEASE_NOTES_v2.0.4.md similarity index 100% rename from RELEASE_NOTES_v2.0.4.md rename to MDfile/RELEASE_NOTES_v2.0.4.md diff --git a/RELEASE_NOTES_v2.0.5.md b/MDfile/RELEASE_NOTES_v2.0.5.md similarity index 100% rename from RELEASE_NOTES_v2.0.5.md rename to MDfile/RELEASE_NOTES_v2.0.5.md diff --git a/RELEASE_NOTES_v2.0.6.md b/MDfile/RELEASE_NOTES_v2.0.6.md similarity index 100% rename from RELEASE_NOTES_v2.0.6.md rename to MDfile/RELEASE_NOTES_v2.0.6.md diff --git a/RELEASE_NOTES_v2.1.0.md b/MDfile/RELEASE_NOTES_v2.1.0.md similarity index 100% rename from RELEASE_NOTES_v2.1.0.md rename to MDfile/RELEASE_NOTES_v2.1.0.md diff --git a/RELEASE_NOTES_v2.1.1.md b/MDfile/RELEASE_NOTES_v2.1.1.md similarity index 100% rename from RELEASE_NOTES_v2.1.1.md rename to MDfile/RELEASE_NOTES_v2.1.1.md diff --git a/RELEASE_NOTES_v2.2.2.md b/MDfile/RELEASE_NOTES_v2.2.2.md similarity index 100% rename from RELEASE_NOTES_v2.2.2.md rename to MDfile/RELEASE_NOTES_v2.2.2.md diff --git a/RELEASE_NOTES_v2.2.3.md b/MDfile/RELEASE_NOTES_v2.2.3.md similarity index 100% rename from RELEASE_NOTES_v2.2.3.md rename to MDfile/RELEASE_NOTES_v2.2.3.md diff --git a/RELEASE_NOTES_v2.2.36.md b/MDfile/RELEASE_NOTES_v2.2.36.md similarity index 100% rename from RELEASE_NOTES_v2.2.36.md rename to MDfile/RELEASE_NOTES_v2.2.36.md diff --git a/RELEASE_NOTES_v2.2.39.md b/MDfile/RELEASE_NOTES_v2.2.39.md similarity index 100% rename from RELEASE_NOTES_v2.2.39.md rename to MDfile/RELEASE_NOTES_v2.2.39.md diff --git a/RELEASE_NOTES_v2.2.40.md b/MDfile/RELEASE_NOTES_v2.2.40.md similarity index 100% rename from RELEASE_NOTES_v2.2.40.md rename to MDfile/RELEASE_NOTES_v2.2.40.md diff --git a/RELEASE_NOTES_v2.2.41.md b/MDfile/RELEASE_NOTES_v2.2.41.md similarity index 100% rename from RELEASE_NOTES_v2.2.41.md rename to MDfile/RELEASE_NOTES_v2.2.41.md diff --git a/RELEASE_NOTES_v2.2.42.md b/MDfile/RELEASE_NOTES_v2.2.42.md similarity index 100% rename from RELEASE_NOTES_v2.2.42.md rename to MDfile/RELEASE_NOTES_v2.2.42.md diff --git a/RELEASE_NOTES_v2.2.43.md b/MDfile/RELEASE_NOTES_v2.2.43.md similarity index 100% rename from RELEASE_NOTES_v2.2.43.md rename to MDfile/RELEASE_NOTES_v2.2.43.md diff --git a/RELEASE_NOTES_v2.2.44.md b/MDfile/RELEASE_NOTES_v2.2.44.md similarity index 100% rename from RELEASE_NOTES_v2.2.44.md rename to MDfile/RELEASE_NOTES_v2.2.44.md diff --git a/RELEASE_NOTES_v2.2.45.md b/MDfile/RELEASE_NOTES_v2.2.45.md similarity index 100% rename from RELEASE_NOTES_v2.2.45.md rename to MDfile/RELEASE_NOTES_v2.2.45.md diff --git a/RELEASE_NOTES_v2.2.46.md b/MDfile/RELEASE_NOTES_v2.2.46.md similarity index 100% rename from RELEASE_NOTES_v2.2.46.md rename to MDfile/RELEASE_NOTES_v2.2.46.md diff --git a/RELEASE_NOTES_v2.2.47.md b/MDfile/RELEASE_NOTES_v2.2.47.md similarity index 100% rename from RELEASE_NOTES_v2.2.47.md rename to MDfile/RELEASE_NOTES_v2.2.47.md diff --git a/RELEASE_NOTES_v2.2.48.md b/MDfile/RELEASE_NOTES_v2.2.48.md similarity index 100% rename from RELEASE_NOTES_v2.2.48.md rename to MDfile/RELEASE_NOTES_v2.2.48.md diff --git a/RELEASE_NOTES_v2.2.49.md b/MDfile/RELEASE_NOTES_v2.2.49.md similarity index 100% rename from RELEASE_NOTES_v2.2.49.md rename to MDfile/RELEASE_NOTES_v2.2.49.md diff --git a/RELEASE_NOTES_v2.2.50.md b/MDfile/RELEASE_NOTES_v2.2.50.md similarity index 100% rename from RELEASE_NOTES_v2.2.50.md rename to MDfile/RELEASE_NOTES_v2.2.50.md diff --git a/RELEASE_NOTES_v2.2.51.md b/MDfile/RELEASE_NOTES_v2.2.51.md similarity index 100% rename from RELEASE_NOTES_v2.2.51.md rename to MDfile/RELEASE_NOTES_v2.2.51.md diff --git a/RELEASE_NOTES_v2.2.52.md b/MDfile/RELEASE_NOTES_v2.2.52.md similarity index 100% rename from RELEASE_NOTES_v2.2.52.md rename to MDfile/RELEASE_NOTES_v2.2.52.md diff --git a/RELEASE_NOTES_v2.2.53.md b/MDfile/RELEASE_NOTES_v2.2.53.md similarity index 100% rename from RELEASE_NOTES_v2.2.53.md rename to MDfile/RELEASE_NOTES_v2.2.53.md diff --git a/RELEASE_NOTES_v2.2.54.md b/MDfile/RELEASE_NOTES_v2.2.54.md similarity index 100% rename from RELEASE_NOTES_v2.2.54.md rename to MDfile/RELEASE_NOTES_v2.2.54.md diff --git a/RELEASE_NOTES_v2.2.56.md b/MDfile/RELEASE_NOTES_v2.2.56.md similarity index 100% rename from RELEASE_NOTES_v2.2.56.md rename to MDfile/RELEASE_NOTES_v2.2.56.md diff --git a/RELEASE_NOTES_v2.2.57.md b/MDfile/RELEASE_NOTES_v2.2.57.md similarity index 100% rename from RELEASE_NOTES_v2.2.57.md rename to MDfile/RELEASE_NOTES_v2.2.57.md diff --git a/RELEASE_NOTES_v2.2.58.md b/MDfile/RELEASE_NOTES_v2.2.58.md similarity index 100% rename from RELEASE_NOTES_v2.2.58.md rename to MDfile/RELEASE_NOTES_v2.2.58.md diff --git a/RELEASE_NOTES_v2.2.59.md b/MDfile/RELEASE_NOTES_v2.2.59.md similarity index 100% rename from RELEASE_NOTES_v2.2.59.md rename to MDfile/RELEASE_NOTES_v2.2.59.md diff --git a/RELEASE_NOTES_v2.2.60.md b/MDfile/RELEASE_NOTES_v2.2.60.md similarity index 100% rename from RELEASE_NOTES_v2.2.60.md rename to MDfile/RELEASE_NOTES_v2.2.60.md diff --git a/RELEASE_NOTES_v2.2.61.md b/MDfile/RELEASE_NOTES_v2.2.61.md similarity index 100% rename from RELEASE_NOTES_v2.2.61.md rename to MDfile/RELEASE_NOTES_v2.2.61.md diff --git a/RELEASE_NOTES_v2.2.62.md b/MDfile/RELEASE_NOTES_v2.2.62.md similarity index 100% rename from RELEASE_NOTES_v2.2.62.md rename to MDfile/RELEASE_NOTES_v2.2.62.md diff --git a/RELEASE_NOTES_v2.2.63.md b/MDfile/RELEASE_NOTES_v2.2.63.md similarity index 100% rename from RELEASE_NOTES_v2.2.63.md rename to MDfile/RELEASE_NOTES_v2.2.63.md diff --git a/RELEASE_NOTES_v2.2.64.md b/MDfile/RELEASE_NOTES_v2.2.64.md similarity index 100% rename from RELEASE_NOTES_v2.2.64.md rename to MDfile/RELEASE_NOTES_v2.2.64.md diff --git a/RELEASE_NOTES_v2.2.81.md b/MDfile/RELEASE_NOTES_v2.2.81.md similarity index 100% rename from RELEASE_NOTES_v2.2.81.md rename to MDfile/RELEASE_NOTES_v2.2.81.md diff --git a/RELEASE_NOTES_v2.2.82.md b/MDfile/RELEASE_NOTES_v2.2.82.md similarity index 100% rename from RELEASE_NOTES_v2.2.82.md rename to MDfile/RELEASE_NOTES_v2.2.82.md diff --git a/RELEASE_NOTES_v2.2.83.md b/MDfile/RELEASE_NOTES_v2.2.83.md similarity index 100% rename from RELEASE_NOTES_v2.2.83.md rename to MDfile/RELEASE_NOTES_v2.2.83.md diff --git a/RELEASE_NOTES_v2.2.84.md b/MDfile/RELEASE_NOTES_v2.2.84.md similarity index 100% rename from RELEASE_NOTES_v2.2.84.md rename to MDfile/RELEASE_NOTES_v2.2.84.md diff --git a/RELEASE_NOTES_v2.2.85.md b/MDfile/RELEASE_NOTES_v2.2.85.md similarity index 100% rename from RELEASE_NOTES_v2.2.85.md rename to MDfile/RELEASE_NOTES_v2.2.85.md diff --git a/RELEASE_NOTES_v2.2.86.md b/MDfile/RELEASE_NOTES_v2.2.86.md similarity index 100% rename from RELEASE_NOTES_v2.2.86.md rename to MDfile/RELEASE_NOTES_v2.2.86.md diff --git a/RELEASE_NOTES_v2.2.87.md b/MDfile/RELEASE_NOTES_v2.2.87.md similarity index 100% rename from RELEASE_NOTES_v2.2.87.md rename to MDfile/RELEASE_NOTES_v2.2.87.md diff --git a/RELEASE_NOTES_v2.3.1.md b/MDfile/RELEASE_NOTES_v2.3.1.md similarity index 100% rename from RELEASE_NOTES_v2.3.1.md rename to MDfile/RELEASE_NOTES_v2.3.1.md diff --git a/RELEASE_NOTES_v2.3.2.md b/MDfile/RELEASE_NOTES_v2.3.2.md similarity index 100% rename from RELEASE_NOTES_v2.3.2.md rename to MDfile/RELEASE_NOTES_v2.3.2.md diff --git a/RELEASE_NOTES_v2.3.3.md b/MDfile/RELEASE_NOTES_v2.3.3.md similarity index 100% rename from RELEASE_NOTES_v2.3.3.md rename to MDfile/RELEASE_NOTES_v2.3.3.md diff --git a/RELEASE_NOTES_v2.3.4.md b/MDfile/RELEASE_NOTES_v2.3.4.md similarity index 100% rename from RELEASE_NOTES_v2.3.4.md rename to MDfile/RELEASE_NOTES_v2.3.4.md diff --git a/RELEASE_NOTES_v2.3.5.md b/MDfile/RELEASE_NOTES_v2.3.5.md similarity index 100% rename from RELEASE_NOTES_v2.3.5.md rename to MDfile/RELEASE_NOTES_v2.3.5.md diff --git a/RELEASE_NOTES_v2.3.6.md b/MDfile/RELEASE_NOTES_v2.3.6.md similarity index 100% rename from RELEASE_NOTES_v2.3.6.md rename to MDfile/RELEASE_NOTES_v2.3.6.md diff --git a/RELEASE_NOTES_v2.3.7.md b/MDfile/RELEASE_NOTES_v2.3.7.md similarity index 100% rename from RELEASE_NOTES_v2.3.7.md rename to MDfile/RELEASE_NOTES_v2.3.7.md diff --git a/RELEASE_NOTES_v2.3.8.md b/MDfile/RELEASE_NOTES_v2.3.8.md similarity index 100% rename from RELEASE_NOTES_v2.3.8.md rename to MDfile/RELEASE_NOTES_v2.3.8.md diff --git a/RELEASE_NOTES_v2.3.md b/MDfile/RELEASE_NOTES_v2.3.md similarity index 100% rename from RELEASE_NOTES_v2.3.md rename to MDfile/RELEASE_NOTES_v2.3.md diff --git a/REMINDER_SYSTEM_IMPLEMENTATION.md b/MDfile/REMINDER_SYSTEM_IMPLEMENTATION.md similarity index 100% rename from REMINDER_SYSTEM_IMPLEMENTATION.md rename to MDfile/REMINDER_SYSTEM_IMPLEMENTATION.md diff --git a/REMINDER_SYSTEM_QUICKSTART.md b/MDfile/REMINDER_SYSTEM_QUICKSTART.md similarity index 100% rename from REMINDER_SYSTEM_QUICKSTART.md rename to MDfile/REMINDER_SYSTEM_QUICKSTART.md diff --git a/SAG_MODULE_COMPLETION_REPORT.md b/MDfile/SAG_MODULE_COMPLETION_REPORT.md similarity index 100% rename from SAG_MODULE_COMPLETION_REPORT.md rename to MDfile/SAG_MODULE_COMPLETION_REPORT.md diff --git a/SAG_MODULE_IMPLEMENTATION_PLAN.md b/MDfile/SAG_MODULE_IMPLEMENTATION_PLAN.md similarity index 100% rename from SAG_MODULE_IMPLEMENTATION_PLAN.md rename to MDfile/SAG_MODULE_IMPLEMENTATION_PLAN.md diff --git a/SAG_MODULE_PLAN.md b/MDfile/SAG_MODULE_PLAN.md similarity index 100% rename from SAG_MODULE_PLAN.md rename to MDfile/SAG_MODULE_PLAN.md diff --git a/SALES_AND_AGGREGATION_PLAN.md b/MDfile/SALES_AND_AGGREGATION_PLAN.md similarity index 100% rename from SALES_AND_AGGREGATION_PLAN.md rename to MDfile/SALES_AND_AGGREGATION_PLAN.md diff --git a/SERVICE_CONTRACT_WIZARD_README.md b/MDfile/SERVICE_CONTRACT_WIZARD_README.md similarity index 100% rename from SERVICE_CONTRACT_WIZARD_README.md rename to MDfile/SERVICE_CONTRACT_WIZARD_README.md diff --git a/TEMPLATES_FINAL_VERIFICATION.md b/MDfile/TEMPLATES_FINAL_VERIFICATION.md similarity index 100% rename from TEMPLATES_FINAL_VERIFICATION.md rename to MDfile/TEMPLATES_FINAL_VERIFICATION.md diff --git a/TEMPLATES_QUICK_REFERENCE.md b/MDfile/TEMPLATES_QUICK_REFERENCE.md similarity index 100% rename from TEMPLATES_QUICK_REFERENCE.md rename to MDfile/TEMPLATES_QUICK_REFERENCE.md diff --git a/TEMPLATE_IMPLEMENTATION_SUMMARY.md b/MDfile/TEMPLATE_IMPLEMENTATION_SUMMARY.md similarity index 100% rename from TEMPLATE_IMPLEMENTATION_SUMMARY.md rename to MDfile/TEMPLATE_IMPLEMENTATION_SUMMARY.md diff --git a/app/core/config.py b/app/core/config.py index 9752e4c..cd5c27b 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -315,6 +315,15 @@ class Settings(BaseSettings): FEDEX_BASE_URL: str = "" FEDEX_TIMEOUT_SECONDS: int = 20 + # ALSO Cloud Marketplace Integration + ALSO_ENABLED: bool = False + ALSO_READ_ONLY: bool = True + ALSO_DRY_RUN: bool = True + ALSO_API_BASE_URL: str = "" + ALSO_API_KEY: str = "" + ALSO_API_SECRET: str = "" + ALSO_TIMEOUT_SECONDS: int = 20 + # Bottom bar module BOTTOM_BAR_ENABLED: bool = False diff --git a/app/customers/backend/router.py b/app/customers/backend/router.py index 6365428..67f2018 100644 --- a/app/customers/backend/router.py +++ b/app/customers/backend/router.py @@ -5,7 +5,7 @@ Adapted from OmniSync for BMC Hub """ from fastapi import APIRouter, HTTPException, Query -from typing import List, Optional, Dict +from typing import List, Optional, Dict, Any from pydantic import BaseModel import logging import asyncio @@ -1780,3 +1780,139 @@ async def get_subscription_billing_matrix( logger.error(f"❌ Error generating billing matrix: {e}") raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/customers/{customer_id}/acmp") +async def get_customer_acmp_overview( + customer_id: int, + months: int = Query(default=6, ge=1, le=24, description="Months for trend data"), +): + """Return ACMP (ALSO Cloud Marketplace) detail and statistics for one customer.""" + try: + customer = execute_query_single("SELECT id, name FROM customers WHERE id = %s", (customer_id,)) + if not customer: + raise HTTPException(status_code=404, detail=f"Customer {customer_id} not found") + + table_check = execute_query_single("SELECT to_regclass('public.also_import_lines') AS table_name") + if not table_check or not table_check.get("table_name"): + return { + "customer_id": customer_id, + "customer_name": customer.get("name"), + "available": False, + "message": "ACMP data is not available yet (missing also_import_lines table)", + "summary": {}, + "status_breakdown": [], + "products": [], + "monthly": [], + } + + summary = execute_query_single( + """ + WITH scoped AS ( + SELECT * + FROM also_import_lines + WHERE matched_customer_id = %s + ) + SELECT + COUNT(*) AS line_count, + COUNT(DISTINCT COALESCE(material_number, product_name, 'ukendt')) AS distinct_products, + COALESCE(SUM(COALESCE(total_price, sales_price, 0)), 0) AS revenue_total, + COALESCE(SUM(COALESCE(cost_amount, 0)), 0) AS cost_total, + COALESCE(SUM(COALESCE(total_price, sales_price, 0) - COALESCE(cost_amount, 0)), 0) AS margin_total, + COUNT(*) FILTER (WHERE queue_status = 'ready_for_approval') AS pending_approvals, + COUNT(*) FILTER (WHERE queue_status = 'error') AS error_count, + COALESCE( + SUM(COALESCE(total_price, sales_price, 0)) FILTER ( + WHERE date_trunc('month', COALESCE(billing_start::timestamp, created_at)) = date_trunc('month', CURRENT_DATE) + ), + 0 + ) AS revenue_month, + COALESCE( + SUM(COALESCE(cost_amount, 0)) FILTER ( + WHERE date_trunc('month', COALESCE(billing_start::timestamp, created_at)) = date_trunc('month', CURRENT_DATE) + ), + 0 + ) AS cost_month + FROM scoped + """, + (customer_id,), + ) or {} + + status_breakdown = execute_query( + """ + SELECT queue_status, COUNT(*)::INTEGER AS count + FROM also_import_lines + WHERE matched_customer_id = %s + GROUP BY queue_status + ORDER BY count DESC, queue_status ASC + """, + (customer_id,), + ) or [] + + products = execute_query( + """ + SELECT + COALESCE(material_number, 'N/A') AS material_number, + COALESCE(vendor, 'N/A') AS vendor, + COALESCE(product_name, 'Ukendt produkt') AS product_name, + COUNT(*)::INTEGER AS line_count, + COALESCE(SUM(COALESCE(billable_parameters, 1)), 0) AS quantity_total, + COALESCE(SUM(COALESCE(total_price, sales_price, 0)), 0) AS revenue_total, + COALESCE(SUM(COALESCE(cost_amount, 0)), 0) AS cost_total, + COALESCE(SUM(COALESCE(total_price, sales_price, 0) - COALESCE(cost_amount, 0)), 0) AS margin_total, + MAX(billing_start) AS last_billing_start, + MAX(created_at) AS last_seen_at + FROM also_import_lines + WHERE matched_customer_id = %s + GROUP BY COALESCE(material_number, 'N/A'), COALESCE(vendor, 'N/A'), COALESCE(product_name, 'Ukendt produkt') + ORDER BY revenue_total DESC, quantity_total DESC + LIMIT 200 + """, + (customer_id,), + ) or [] + + monthly = execute_query( + """ + SELECT + TO_CHAR(date_trunc('month', COALESCE(billing_start::timestamp, created_at)), 'YYYY-MM') AS month, + COALESCE(SUM(COALESCE(billable_parameters, 1)), 0) AS quantity_total, + COALESCE(SUM(COALESCE(total_price, sales_price, 0)), 0) AS revenue_total, + COALESCE(SUM(COALESCE(cost_amount, 0)), 0) AS cost_total, + COALESCE(SUM(COALESCE(total_price, sales_price, 0) - COALESCE(cost_amount, 0)), 0) AS margin_total, + COUNT(*)::INTEGER AS lines + FROM also_import_lines + WHERE matched_customer_id = %s + AND date_trunc('month', COALESCE(billing_start::timestamp, created_at)) >= date_trunc('month', CURRENT_DATE) - ((%s - 1) * INTERVAL '1 month') + GROUP BY date_trunc('month', COALESCE(billing_start::timestamp, created_at)) + ORDER BY month ASC + """, + (customer_id, months), + ) or [] + + response: Dict[str, Any] = { + "customer_id": customer_id, + "customer_name": customer.get("name"), + "available": True, + "summary": { + "line_count": int(summary.get("line_count") or 0), + "distinct_products": int(summary.get("distinct_products") or 0), + "revenue_total": summary.get("revenue_total") or 0, + "cost_total": summary.get("cost_total") or 0, + "margin_total": summary.get("margin_total") or 0, + "revenue_month": summary.get("revenue_month") or 0, + "cost_month": summary.get("cost_month") or 0, + "margin_month": (summary.get("revenue_month") or 0) - (summary.get("cost_month") or 0), + "pending_approvals": int(summary.get("pending_approvals") or 0), + "error_count": int(summary.get("error_count") or 0), + }, + "status_breakdown": status_breakdown, + "products": products, + "monthly": monthly, + } + return response + + except HTTPException: + raise + except Exception as e: + logger.error("❌ Error fetching ACMP overview for customer %s: %s", customer_id, e) + raise HTTPException(status_code=500, detail=str(e)) + diff --git a/app/customers/frontend/customer_detail.html b/app/customers/frontend/customer_detail.html index f420252..af6dcf8 100644 --- a/app/customers/frontend/customer_detail.html +++ b/app/customers/frontend/customer_detail.html @@ -62,6 +62,24 @@ border-radius: 12px; padding: 1.5rem; } + + .acmp-stat-card { + background: var(--bg-card); + border: 1px solid rgba(15, 76, 117, 0.14); + border-radius: 12px; + padding: 1rem; + } + + .acmp-stat-label { + color: var(--text-secondary); + font-size: 0.85rem; + } + + .acmp-stat-value { + font-size: 1.2rem; + font-weight: 700; + color: var(--text-primary); + } .info-row { display: flex; @@ -228,9 +246,33 @@ .contacts-panel { background: var(--bg-card); - border: 1px solid rgba(0, 0, 0, 0.08); + border: 1px solid rgba(15, 76, 117, 0.14); border-radius: 14px; overflow: hidden; + box-shadow: 0 10px 28px rgba(15, 76, 117, 0.08); + } + + .contacts-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.65rem; + margin-bottom: 0.9rem; + padding: 0.78rem 0.9rem; + background: linear-gradient(135deg, rgba(15, 76, 117, 0.08) 0%, rgba(15, 76, 117, 0.02) 100%); + border: 1px solid rgba(15, 76, 117, 0.12); + border-radius: 12px; + } + + .contacts-search { + max-width: 460px; + min-width: 280px; + } + + .contacts-search .form-control, + .contacts-search .input-group-text { + border-color: rgba(15, 76, 117, 0.2); } .contacts-table { @@ -259,6 +301,18 @@ color: var(--text-primary); } + .contacts-table .contact-name-link { + color: var(--text-primary); + text-decoration: none; + border-bottom: 1px solid transparent; + transition: color 0.15s ease, border-color 0.15s ease; + } + + .contacts-table .contact-name-link:hover { + color: var(--accent); + border-color: rgba(15, 76, 117, 0.35); + } + .contacts-table .contact-email a { overflow-wrap: anywhere; } @@ -267,6 +321,7 @@ white-space: nowrap; } + .contacts-table .contact-phone-wrap, .contacts-table .contact-mobile-wrap { display: inline-flex; align-items: center; @@ -274,6 +329,12 @@ flex-wrap: wrap; } + .contacts-table .btn-voip { + border-color: rgba(25, 135, 84, 0.6); + color: #198754; + } + + .contacts-table .contact-phone-wrap .btn, .contacts-table .contact-mobile-wrap .btn { padding: 0.18rem 0.52rem; line-height: 1.2; @@ -285,6 +346,16 @@ } @media (max-width: 992px) { + .contacts-toolbar { + padding: 0.7rem; + } + + .contacts-search { + max-width: none; + min-width: 0; + width: 100%; + } + #contactsContainer { min-width: 920px; } @@ -406,6 +477,11 @@ Abonnements Matrix +