- Added multiple test cases for the sag module to ensure proper functionality and data handling. - Created new templates for knowledge detail and knowledge index pages to display articles and solutions. - Introduced migrations to enhance the internet connections schema, including new columns for manual sharing and SLA subscriptions. - Added a script to reconcile known internet connections with verified data. - Planned the implementation of a new website content administration module for managing customer references and operational status.
251 lines
9.9 KiB
Python
251 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Reset internet connection data and rebuild from latest GlobalConnect extractions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from collections import defaultdict
|
|
|
|
from app.billing.backend.supplier_invoices import _sync_globalconnect_extraction_to_internet
|
|
from app.core.database import execute_query, execute_query_single, execute_update, init_db
|
|
|
|
|
|
def latest_globalconnect_extractions() -> list[dict]:
|
|
rows = execute_query(
|
|
"""
|
|
SELECT DISTINCT ON (e.file_id)
|
|
e.extraction_id,
|
|
e.file_id,
|
|
e.vendor_name,
|
|
e.document_id,
|
|
e.document_date,
|
|
e.created_at,
|
|
i.filename
|
|
FROM extractions e
|
|
JOIN incoming_files i ON i.file_id = e.file_id
|
|
WHERE LOWER(COALESCE(e.vendor_name, '')) LIKE '%%globalconnect%%'
|
|
ORDER BY e.file_id, e.created_at DESC, e.extraction_id DESC
|
|
"""
|
|
) or []
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def snapshot_manual_allocations() -> list[dict]:
|
|
"""Keep deliberate CRM ownership across a destructive invoice rebuild.
|
|
|
|
Supplier imports must never infer a customer. A clean rebuild previously
|
|
also discarded customers that a user had already selected, which allowed a
|
|
later, shifted invoice address to become the new canonical address.
|
|
"""
|
|
rows = execute_query(
|
|
"""
|
|
SELECT DISTINCT ON (
|
|
regexp_replace(UPPER(COALESCE(circuit_number, '')), '[^A-Z0-9]', '', 'g')
|
|
)
|
|
regexp_replace(UPPER(COALESCE(circuit_number, '')), '[^A-Z0-9]', '', 'g') AS reference_key,
|
|
circuit_number,
|
|
customer_id,
|
|
address
|
|
FROM internet_connections_connections
|
|
WHERE deleted_at IS NULL
|
|
AND customer_id IS NOT NULL
|
|
AND BTRIM(COALESCE(circuit_number, '')) <> ''
|
|
ORDER BY
|
|
regexp_replace(UPPER(COALESCE(circuit_number, '')), '[^A-Z0-9]', '', 'g'),
|
|
updated_at DESC,
|
|
id DESC
|
|
"""
|
|
) or []
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def restore_manual_allocations(allocations: list[dict]) -> int:
|
|
restored = 0
|
|
for allocation in allocations:
|
|
reference_key = str(allocation.get("reference_key") or "").strip()
|
|
if not reference_key:
|
|
continue
|
|
connection = execute_query_single(
|
|
"""
|
|
SELECT id
|
|
FROM internet_connections_connections
|
|
WHERE deleted_at IS NULL
|
|
AND regexp_replace(UPPER(COALESCE(circuit_number, '')), '[^A-Z0-9]', '', 'g') = %s
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
""",
|
|
(reference_key,),
|
|
)
|
|
if not connection:
|
|
continue
|
|
connection_id = int(connection["id"])
|
|
execute_update(
|
|
"""
|
|
UPDATE internet_connections_connections
|
|
SET customer_id = %s,
|
|
address = COALESCE(NULLIF(BTRIM(%s), ''), address),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = %s
|
|
""",
|
|
(allocation.get("customer_id"), allocation.get("address"), connection_id),
|
|
)
|
|
if str(allocation.get("address") or "").strip():
|
|
execute_update(
|
|
"""
|
|
UPDATE internet_connections_ip_ranges
|
|
SET service_address = %s,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE connection_id = %s AND deleted_at IS NULL
|
|
""",
|
|
(allocation["address"], connection_id),
|
|
)
|
|
restored += 1
|
|
return restored
|
|
|
|
|
|
def reset_all_internet_data() -> dict:
|
|
summary: dict[str, int] = {}
|
|
counts = execute_query_single(
|
|
"""
|
|
SELECT
|
|
(SELECT COUNT(*) FROM internet_connections_connections WHERE deleted_at IS NULL) AS connections,
|
|
(SELECT COUNT(*) FROM internet_connections_ip_ranges WHERE deleted_at IS NULL) AS ip_ranges,
|
|
(SELECT COUNT(*) FROM internet_connections_ip_addresses WHERE deleted_at IS NULL) AS ip_addresses,
|
|
(SELECT COUNT(*) FROM internet_connections_pricing) AS pricing,
|
|
(SELECT COUNT(*) FROM internet_connections_history) AS history
|
|
"""
|
|
) or {}
|
|
summary["before_connections"] = int(counts.get("connections") or 0)
|
|
summary["before_ip_ranges"] = int(counts.get("ip_ranges") or 0)
|
|
summary["before_ip_addresses"] = int(counts.get("ip_addresses") or 0)
|
|
summary["before_pricing"] = int(counts.get("pricing") or 0)
|
|
summary["before_history"] = int(counts.get("history") or 0)
|
|
|
|
summary["deleted_ip_addresses"] = execute_update(
|
|
"""
|
|
UPDATE internet_connections_ip_addresses
|
|
SET deleted_at = CURRENT_TIMESTAMP,
|
|
updated_at = CURRENT_TIMESTAMP,
|
|
comment = CONCAT(
|
|
COALESCE(comment, ''),
|
|
CASE WHEN COALESCE(comment, '') = '' THEN '' ELSE E'\n' END,
|
|
'Nulstillet før ren genimport af internetforbindelser.'
|
|
)
|
|
WHERE deleted_at IS NULL
|
|
"""
|
|
)
|
|
summary["deleted_ip_ranges"] = execute_update(
|
|
"""
|
|
UPDATE internet_connections_ip_ranges
|
|
SET deleted_at = CURRENT_TIMESTAMP,
|
|
updated_at = CURRENT_TIMESTAMP,
|
|
description = CONCAT(
|
|
COALESCE(description, ''),
|
|
CASE WHEN COALESCE(description, '') = '' THEN '' ELSE E'\n' END,
|
|
'Nulstillet før ren genimport af internetforbindelser.'
|
|
)
|
|
WHERE deleted_at IS NULL
|
|
"""
|
|
)
|
|
summary["deleted_connections"] = execute_update(
|
|
"""
|
|
UPDATE internet_connections_connections
|
|
SET deleted_at = CURRENT_TIMESTAMP,
|
|
updated_at = CURRENT_TIMESTAMP,
|
|
status = 'inactive',
|
|
notes = CONCAT(
|
|
COALESCE(notes, ''),
|
|
CASE WHEN COALESCE(notes, '') = '' THEN '' ELSE E'\n' END,
|
|
'Nulstillet før ren genimport af internetforbindelser.'
|
|
)
|
|
WHERE deleted_at IS NULL
|
|
"""
|
|
)
|
|
summary["deleted_pricing"] = execute_update("DELETE FROM internet_connections_pricing")
|
|
summary["deleted_history"] = execute_update("DELETE FROM internet_connections_history")
|
|
return summary
|
|
|
|
|
|
def rebuild_from_globalconnect(manual_allocations: list[dict] | None = None) -> dict:
|
|
extractions = latest_globalconnect_extractions()
|
|
results = []
|
|
totals = defaultdict(int)
|
|
|
|
for extraction_stub in extractions:
|
|
extraction = execute_query_single(
|
|
"""
|
|
SELECT *
|
|
FROM extractions
|
|
WHERE extraction_id = %s
|
|
""",
|
|
(extraction_stub["extraction_id"],),
|
|
)
|
|
if not extraction:
|
|
continue
|
|
# A reset intentionally rebuilds previously processed invoices, so the
|
|
# normal idempotency guard must not skip their historical sync runs.
|
|
result = _sync_globalconnect_extraction_to_internet(dict(extraction), force=True)
|
|
result_summary = {
|
|
"file_id": extraction_stub["file_id"],
|
|
"extraction_id": extraction_stub["extraction_id"],
|
|
"filename": extraction_stub["filename"],
|
|
"document_id": extraction_stub["document_id"],
|
|
"connections_synced": int(result.get("connections_synced") or 0),
|
|
"connections_created": int(result.get("connections_created") or 0),
|
|
"connections_updated": int(result.get("connections_updated") or 0),
|
|
"ip_ranges_synced": int(result.get("ip_ranges_synced") or 0),
|
|
"skipped_connection_lines": int(result.get("skipped_connection_lines") or 0),
|
|
"skipped_ip_range_lines": int(result.get("skipped_orphan_ip_ranges") or 0),
|
|
"verification": result.get("verification") or {},
|
|
}
|
|
results.append(result_summary)
|
|
totals["files_processed"] += 1
|
|
totals["connections_synced"] += result_summary["connections_synced"]
|
|
totals["connections_created"] += result_summary["connections_created"]
|
|
totals["connections_updated"] += result_summary["connections_updated"]
|
|
totals["ip_ranges_synced"] += result_summary["ip_ranges_synced"]
|
|
totals["skipped_connection_lines"] += result_summary["skipped_connection_lines"]
|
|
totals["skipped_ip_range_lines"] += result_summary["skipped_ip_range_lines"]
|
|
|
|
restored_allocations = restore_manual_allocations(manual_allocations or [])
|
|
counts = execute_query_single(
|
|
"""
|
|
SELECT
|
|
(SELECT COUNT(*) FROM internet_connections_connections WHERE deleted_at IS NULL) AS active_connections,
|
|
(SELECT COUNT(*) FROM internet_connections_connections WHERE deleted_at IS NULL AND (address IS NULL OR BTRIM(address) = '')) AS missing_address_connections,
|
|
(SELECT COUNT(*) FROM internet_connections_ip_ranges WHERE deleted_at IS NULL) AS active_ip_ranges,
|
|
(SELECT COUNT(*) FROM internet_connections_ip_addresses WHERE deleted_at IS NULL) AS active_ip_addresses
|
|
"""
|
|
) or {}
|
|
return {
|
|
"totals": dict(totals),
|
|
"results": results,
|
|
"post_counts": {
|
|
"active_connections": int(counts.get("active_connections") or 0),
|
|
"missing_address_connections": int(counts.get("missing_address_connections") or 0),
|
|
"active_ip_ranges": int(counts.get("active_ip_ranges") or 0),
|
|
"active_ip_addresses": int(counts.get("active_ip_addresses") or 0),
|
|
},
|
|
"restored_manual_allocations": restored_allocations,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--skip-rebuild", action="store_true", help="Only reset internet data")
|
|
args = parser.parse_args()
|
|
|
|
init_db()
|
|
manual_allocations = snapshot_manual_allocations()
|
|
payload = {
|
|
"reset": reset_all_internet_data(),
|
|
"rebuild": None if args.skip_rebuild else rebuild_from_globalconnect(manual_allocations),
|
|
}
|
|
print(json.dumps(payload, ensure_ascii=False, indent=2, default=str))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|