mirror of
https://github.com/itsdave-de/msp.git
synced 2026-09-28 04:02:42 -03:00
chore(msp): Dev-/Test-Helfer für Supplier-Import (nur Entwicklungs-Hilfsmittel)
Nicht-produktive Helper-Module im importers/-Paket, mit Unterstrich-Präfix markiert. Werden von keiner Produktiv-Codebasis aufgerufen, sondern nur manuell über `bench execute`: - _smoke_test: parst eine CSV und gibt Verteilungsstatistiken aus (distinct customers/products, enums). - _runner: Convenience-Wrapper, um einen bestehenden Import-Run per Namen zu starten. - _inspect: liefert kompakte JSON-Übersicht über einen Import-Run (Status, Statistik, erste Zeilen) zur Fehlersuche. - _batchtest: importiert mehrere CSVs chronologisch und summiert Subscription-/Event-Zahlen. - _cleanup: löscht alles, was vom neuen Importer erzeugt wurde. Nur für Dev-/Test-Instanzen gedacht; prüft vorab auf test-DB oder developer_mode.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
"""Batch-Test: mehrere ADN-CSVs chronologisch importieren."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import frappe
|
||||
|
||||
from msp.importers._runner import run as run_one
|
||||
|
||||
|
||||
CSV_SEQUENCE = [
|
||||
"/private/files/433148_Rechnungen_17_10_2025.csv",
|
||||
"/private/files/433148_Rechnungen_17_11_2025.csv",
|
||||
"/private/files/433148_Rechnungen_18_12_2025.csv",
|
||||
"/private/files/433148_Rechnungen_16_01_2026.csv",
|
||||
"/private/files/433148_Rechnungen_16_02_2026.csv",
|
||||
"/private/files/433148_Rechnungen_19_03_2026.csv",
|
||||
]
|
||||
|
||||
|
||||
def run(profile: str = "ADN Monthly CSV v1") -> str:
|
||||
results = []
|
||||
for path in CSV_SEQUENCE:
|
||||
abs_check = os.path.join(frappe.get_site_path(), "private", "files", os.path.basename(path))
|
||||
abs_check = os.path.abspath(abs_check)
|
||||
if not os.path.exists(abs_check):
|
||||
results.append({"file": path, "skipped": "file not found"})
|
||||
continue
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Supplier Import Run",
|
||||
"profile": profile,
|
||||
"file": path,
|
||||
"output_mode": "Sales Invoice",
|
||||
})
|
||||
doc.insert(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
summary = run_one(doc.name)
|
||||
results.append({"file": path, "run": doc.name, **summary})
|
||||
# Supply-Subscription-Bestand ermitteln
|
||||
subs_total = frappe.db.count("Supply Subscription")
|
||||
events_total = frappe.db.count("Supply Subscription Event")
|
||||
return json.dumps({
|
||||
"runs": results,
|
||||
"subscriptions_total": subs_total,
|
||||
"events_total": events_total,
|
||||
}, default=str, indent=2, ensure_ascii=False)
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Cleanup-Helper: löscht alles, was der neue Importer erzeugt hat (nur Test!)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def cleanup_all():
|
||||
"""Entfernt sämtliche vom neuen Importer angelegten Daten (Test-System only!)."""
|
||||
if "test" not in frappe.local.conf.get("db_name", "") and not frappe.local.conf.get("developer_mode"):
|
||||
raise Exception("Cleanup nur in Entwicklungs-/Test-Umgebungen erlaubt.")
|
||||
|
||||
# 1. Supply Subscription Events → 2. Supply Subscriptions
|
||||
frappe.db.sql("DELETE FROM `tabSupply Subscription Event`")
|
||||
frappe.db.sql("DELETE FROM `tabSupply Subscription`")
|
||||
|
||||
# 3. Supplier Import Lines (child) → 4. Runs
|
||||
frappe.db.sql("DELETE FROM `tabSupplier Import Line`")
|
||||
frappe.db.sql("DELETE FROM `tabSupplier Import Run`")
|
||||
|
||||
# 5. Draft Sales Invoices, die über den neuen Importer erzeugt wurden
|
||||
# (Heuristik: aus Supplier Import Line.target_doc_name wären sie referenziert
|
||||
# gewesen — da wir die Lines gerade gelöscht haben, nehmen wir stattdessen
|
||||
# alle Draft-SIs mit unserem supply_subscription-Link als Anzeichen.)
|
||||
draft_sis = frappe.db.sql_list("""
|
||||
SELECT DISTINCT parent FROM `tabSales Invoice Item`
|
||||
WHERE supply_subscription IS NOT NULL AND supply_subscription != ''
|
||||
""")
|
||||
for name in draft_sis:
|
||||
doc = frappe.get_doc("Sales Invoice", name)
|
||||
if doc.docstatus == 0:
|
||||
doc.delete()
|
||||
|
||||
# 6. Draft Delivery Notes entsprechend
|
||||
draft_dns = frappe.db.sql_list("""
|
||||
SELECT DISTINCT parent FROM `tabDelivery Note Item`
|
||||
WHERE supply_subscription IS NOT NULL AND supply_subscription != ''
|
||||
""")
|
||||
for name in draft_dns:
|
||||
doc = frappe.get_doc("Delivery Note", name)
|
||||
if doc.docstatus == 0:
|
||||
doc.delete()
|
||||
|
||||
frappe.db.commit()
|
||||
return {
|
||||
"supply_subscriptions_deleted": True,
|
||||
"import_runs_deleted": True,
|
||||
"sales_invoices_cleaned": len(draft_sis),
|
||||
"delivery_notes_cleaned": len(draft_dns),
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Inspect-Helpers für die Entwicklung."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import frappe
|
||||
|
||||
|
||||
def inspect_run(name: str, show_lines: int = 5) -> str:
|
||||
run = frappe.get_doc("Supplier Import Run", name)
|
||||
lines = run.lines[:show_lines]
|
||||
return json.dumps({
|
||||
"status": run.status,
|
||||
"file": run.file,
|
||||
"line_count": run.line_count,
|
||||
"document_count": run.document_count,
|
||||
"error_count": run.error_count,
|
||||
"log_tail": (run.log or "")[-2000:],
|
||||
"first_lines": [
|
||||
{
|
||||
"customer_external_ref": l.customer_external_ref,
|
||||
"customer": l.customer,
|
||||
"vendor_product_id": l.vendor_product_id,
|
||||
"qty": l.qty,
|
||||
"amount": l.amount,
|
||||
"line_status": l.line_status,
|
||||
"error_message": l.error_message,
|
||||
"target": f"{l.target_doc_type or ''} {l.target_doc_name or ''}",
|
||||
}
|
||||
for l in lines
|
||||
],
|
||||
}, default=str, ensure_ascii=False, indent=2)
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Convenience-Helper für Bench-Tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import frappe
|
||||
|
||||
from msp.importers.run_orchestrator import execute_run
|
||||
|
||||
|
||||
def run(name: str) -> dict:
|
||||
"""Startet einen bestehenden Supplier Import Run anhand seines Namens."""
|
||||
doc = frappe.get_doc("Supplier Import Run", name)
|
||||
return execute_run(doc)
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Temporärer Smoke-Test für den ADN-Parser.
|
||||
|
||||
Aufruf:
|
||||
bench --site <site> execute msp.importers._smoke_test.run --kwargs '{"path":"..."}'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
import json
|
||||
|
||||
from msp.importers.registry import resolve
|
||||
|
||||
|
||||
def run(path: str) -> str:
|
||||
parser = resolve("adn_monthly_csv_v1")
|
||||
result = parser.parse(path)
|
||||
|
||||
counters = {
|
||||
"rows": len(result.rows),
|
||||
"format_drift": result.format_drift,
|
||||
"warnings": result.warnings,
|
||||
"invoice_date": str(result.invoice_date) if result.invoice_date else None,
|
||||
}
|
||||
by_customer = Counter(r.customer_external_ref for r in result.rows)
|
||||
by_product = Counter(r.vendor_product_id for r in result.rows)
|
||||
by_billing_plan = Counter(r.billing_plan for r in result.rows)
|
||||
by_term = Counter(r.term_duration for r in result.rows)
|
||||
by_booking = Counter(r.booking_type for r in result.rows)
|
||||
doc_types = Counter(r.document_type for r in result.rows)
|
||||
|
||||
sample = result.rows[0] if result.rows else None
|
||||
sample_dump = None
|
||||
if sample:
|
||||
sample_dump = {
|
||||
"customer_external_ref": sample.customer_external_ref,
|
||||
"vendor_product_id": sample.vendor_product_id,
|
||||
"qty": sample.qty,
|
||||
"amount": sample.amount,
|
||||
"period_start": str(sample.period_start) if sample.period_start else None,
|
||||
"period_end": str(sample.period_end) if sample.period_end else None,
|
||||
"subscription_external_id": sample.subscription_external_id,
|
||||
"billing_plan": sample.billing_plan,
|
||||
"term_duration": sample.term_duration,
|
||||
"booking_type": sample.booking_type,
|
||||
}
|
||||
|
||||
return json.dumps({
|
||||
"counters": counters,
|
||||
"by_customer_top": by_customer.most_common(10),
|
||||
"by_product_top": by_product.most_common(10),
|
||||
"by_billing_plan": by_billing_plan,
|
||||
"by_term_duration": by_term,
|
||||
"by_booking_type": by_booking,
|
||||
"document_types": doc_types,
|
||||
"distinct_customers": len(by_customer),
|
||||
"distinct_products": len(by_product),
|
||||
"sample_first_row": sample_dump,
|
||||
}, default=str, ensure_ascii=False, indent=2)
|
||||
Reference in New Issue
Block a user