get_attendance_list fragte den DocType `Attendance` ab, den es seit dem
v16-Stand ohne hrms nicht mehr gibt. Der DoesNotExistError riss die
gesamte Kette get_data -> compare_hours -> get_target_hours mit, der
Dashboard-Chart "Meine Performance" blieb fuer alle Nutzer auf "Loading"
haengen (ITSD-22440).
Quelle sind jetzt genehmigte HR Absence Requests, deren Abwesenheitsart
das Soll reduziert (HR Absence Type.affects_target_hours). Halbe Tage
(half_day_start/half_day_end) halbieren das Tagessoll.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Im Desk wird die Sidebar aus Desktop Icons (legacy module icons)
gerendert, nicht aus Workspaces direkt. Es gibt ein Sidebar-Icon
'Technik' (= Workspace Technik), aber keins für 'MSP' — daher war
der vorhin in Workspace MSP eingehängte Dropzone-Link über die UI
unerreichbar.
Lieferanten-Import-Karte + 'Datei importieren'-Shortcut jetzt im
Workspace Technik (also unter dem Sidebar-Icon, das tatsächlich
existiert). MSP-Workspace bleibt zusätzlich befüllt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die Drag-&-Drop-Dropzone war zwar als Frappé-Page registriert, aber
nicht im MSP-Workspace verlinkt. Über die UI war sie damit nur per
URL erreichbar.
- Neuer 'Datei importieren'-Shortcut (blau) ganz oben im Workspace
- Erster Link in der Karte 'Lieferanten-Import': 'Datei importieren
(Dropzone)' direkt vor den Import-Run-Listen
Hinweis: Die Workspace-Anpassung erfordert einen Cache-Refresh und
ein Neu-Generieren des content-Felds (Frappés Workspace nutzt EditorJS-
Blocks); bei rein DB-seitigen Inserts in tabWorkspace Link/Shortcut
muss tabWorkspace.content neu gebaut werden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validierung der Hornet-Pipeline gegen drei historische Monate
(10.2025 / 11.2025 / 02.2026, je 16 Sales Invoices in der Test-DB).
Pro Customer und in Summe identisch zu den vom Legacy-adnconnect-
Modul erzeugten Belegen (z. B. Südsee-Camp 634,50 €, Saxlund 234 €,
KFS Fensterbau 162 €, Total 1.836,36 €).
Notwendige Korrekturen unterwegs:
- Hornet-Parser: ENDKUNDE_REFERENCE ist in den realen ADN-Exporten
praktisch immer leer; die Mail-Domain steht in ENDKUNDE. Legacy-
Verhalten (adnconnect.adn_hornet_import) gespiegelt: bei leerem
Reference-Feld auf ENDKUNDE als Domain zurückfallen, sofern es wie
eine Domain aussieht.
- Sniff inhaltsbasiert: ADN exportiert für Microsoft und Hornet
dieselbe Spaltenstruktur (inkl. BILLINGPLAN/MSERP/VERTRAGSDAUER) —
rein kolumnenbasierte Discrimination versagt. Beide Handler
inspizieren jetzt die ersten Datenzeilen: Hornet gewinnt, wenn die
MS-Spalten leer sind, MS gewinnt, wenn sie Werte tragen. Mit echten
CSVs verifiziert (Hornet 1.00/MS 0.55 vs. MS 1.00/Hornet 0.33).
- Title-Template kennt jetzt {invoice_month} (CSV-DATUM, MM.YYYY) —
ADN stellt für Hornet stets im Folgemonat in Rechnung; Legacy nutzt
den Rechnungsmonat im Title, nicht die Wartungsperiode. Hornet-
Profil-Default angepasst.
- DocumentBuilder annotiert erfolgreiche Outcomes mit (target_doctype,
target_name, resolved_customer). Der Run-Orchestrator-Persist
verlinkt Supplier Import Lines damit auch dann zurück, wenn keine
Supply-Subscription-Events erzeugt wurden (Hornet ohne Vertrag-
Spalte). Behebt 'customer=null'-Anzeige im Wizard-Step-3 und die
leere Dokumenten-Tabelle.
- run_summary / run_documents zeigen jetzt das Verkaufs-Total der
erzeugten Sales-Invoice/Delivery-Note (vorher: ADN-Einkaufspreis aus
Supplier Import Line.amount, was bei Hornet ≠ Verkaufspreis ist).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher konnte der MSP-Importer nur die monatliche Microsoft-CSV von ADN
verarbeiten. ADN liefert dieselbe Dateifamilie auch für Hornetsecurity-
Abrechnungen, bisher manuell oder über das alte adnconnect-Modul
verbucht (493 historische Sales Invoices im System).
Neuer Handler "ADN Hornet CSV (Hornetsecurity)" — zweiter Eintrag in
der Auto-Detection der Dropzone, gleiches Wizard-Erlebnis. Hornet-
spezifische Punkte:
- Subscription-Identifikation via 'Vertrag' (statt SUBSCRIPTION_ID_EXTERNAL)
- Mengen werden anteilig auf den Abrechnungsmonat skaliert
(Wartungsbeginn/-ende ergeben einen Tagesanteil — voller Monat → 1.0)
- Customer-Auflösung über das Custom-Field 'hornet_domain' statt
direktem Customer-Name-Lookup
Generische Erweiterungen, von beiden Profilen nutzbar:
- Supplier Import Profile.customer_lookup_field: optionales Custom-Field
für Customer-Resolution per Domain/Identifier
- resolve_customer() akzeptiert lookup_field-Argument
- Microsoft-Sniff bekommt Discriminator-Bonus auf BILLINGPLAN/MSERP/
VERTRAGSDAUER, damit die beiden Handler bei strukturell ähnlichen
CSVs nicht beide 100% scoren
Sniff-Validierung:
Hornet-CSV (Filename "hornet"): Hornet 1.00 / MS 0.70 → Hornet
Hornet-CSV (Filename "Rechnungen"): Hornet 0.95 / MS 0.70 → Hornet
Microsoft-CSV (real): Hornet 0.38 / MS 1.00 → Microsoft
End-to-end gegen synthetische Hornet-CSV verifiziert: SI mit Title
"Abrechnung Hornetsecurity 04.2026", Customer korrekt über
hornet_domain aufgelöst, anteilige Menge (13,33 statt 25 für 16/30
Tage) korrekt berechnet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher wurde der Titel hart als '{title_prefix} {period}' gebaut.
Jetzt zwei optionale Felder im Supplier Import Profile:
- title_template (für Rechnungen / Lieferscheine)
- title_template_credit_note (für Gutschriften)
Verfügbare Platzhalter:
{prefix} — bisheriges title_prefix / title_prefix_credit_note
{period} — z. B. "09.2025" oder "09.2025 – 03.2026"
{customer} — Customer-Ref (CUST-…)
{customer_name} — Anzeigename des Kunden
{vendor} — Vendor-Feld des Profils (z. B. "Microsoft")
{posting_date} — DD.MM.YYYY
{document_type} — "Sales Invoice" / "Delivery Note"
Backwards-compatible: leeres Template fällt auf das alte Verhalten
zurück. Unbekannte Platzhalter werden geloggt und der Builder fällt
ebenfalls auf {prefix} {period} zurück, statt einen Run zu kippen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Beim Klick auf "Datei auswählen" passierte in manchen Browser-Setups
nichts, weil das versteckte <input type=file hidden> per display:none
aus dem Hit-Test verschwindet — und ein .click() darauf öffnet den
Native-Picker dann nicht.
Lösung: Standard "visually hidden"-Pattern (1px im Layout, geclippt,
nicht display:none). Damit funktioniert der programmatische Klick
zuverlässig, ohne dass der Input sichtbar ist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frappés --primary ist gray-900 (=fast schwarz), daher waren der Orb
und die Aurora im vorherigen Cleanup tatsächlich farblos geworden.
Dekoratives Styling jetzt mit eigenen Hex-Tönen:
- Aurora: 4 radiale Gradients (Blau, Violett, Itsdave-Rot-Hauch,
Cyan-Highlight) auf einem überdimensionierten ::before-Layer mit
langsamer Drift-Animation (msp-aurora 18s).
- Drag-over: Animation beschleunigt + Saturate/Brightness-Filter.
- Orb: 60px-Kreis mit Radial-Highlight, blauem Linear-Gradient
(#2490ef → #1565b0 → #0d3f7a), pulsierendem Glow-Ring und
Inset-Shadow für 3D-Tiefe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10 End-to-End-Testszenarien durchgespielt (Drag&Drop, ZIP, Multi-File,
Idempotenz, Unmatched, Error, Doc-Link, Reload-ohne-Kontext, Format-Drift,
Gutschriften). 3 echte Bugs gefunden und behoben, 2 UX-Verbesserungen
ergänzt.
Fixes:
- ZIP-Auto-Detection war zu restriktiv: sniff() hat sich auf Namens-
Heuristik verlassen (confidence 0.85 nur bei 'rechnungen'/'433148' im
Dateinamen, sonst 0.3 → unter Cutoff). Umbenannte ZIPs wurden nicht
erkannt. Fix: ZIP in-memory öffnen, erste CSV-Zeile lesen,
Signatur-Spalten matchen — gleiche Logik wie bei direkten CSVs.
Zusätzlich liest analyze_file bei ZIPs die komplette Datei (ZIP
Central Directory steht am Ende und passt oft nicht in 8 KiB).
- Unmatched-Dateien landeten im Error-State statt im Dropdown-Flow.
Backend hat error="Kein passender Handler …" gesetzt, Frontend
triggerte dadurch set_error(). Fix: analyze_file gibt für Unmatched
jetzt error=null zurück, handler_key=null — das Frontend bietet den
Dropdown mit manueller Handler-Zuordnung an (war schon vorhanden,
wurde aber nie erreicht).
- Fehler-Meldungen im Wizard-Ergebnis zeigten "[object Object]" statt
Text. Frappe-RPC-Errors liefern die Nachricht in _server_messages
als JSON-String. Fix: extract_error_message() parst das sauber aus
(mit Fallback auf exception / message / JSON-Stringify).
UX-Verbesserungen:
- Drift-Checkbox ("Abweichendes Format akzeptieren") ist jetzt immer
sichtbar, nicht nur wenn die Analyse Drift erkannt hat. Hilfreich
beim manuellen Handler-Zuordnungsflow, wenn der User eine Datei
trotz Parser-Ablehnung forcen will. Der Hinweistext passt sich dem
Kontext an.
- Wizard lädt bei fehlender Preview (z. B. Aufruf ohne Dropzone-
Voranalyse) selbst analyze_file, um Stats und Format-Drift-Status
nachzuziehen. Kein "ohne Stats"-Leerzustand mehr im Standardpfad.
Testresultate im Überblick (alle 10 ✓):
1. Drag & Drop: Karte erscheint, 100% Confidence
2. ZIP: nach Fix 100% Confidence (vorher 0)
3. Multi-File (4 parallel): alle Karten nebeneinander
4. Idempotenz: 0 Docs, 116 Skipped, Status Succeeded
5. Unmatched-Flow: Dropdown + disabled Button bis Handler gewählt
6. Broken file: ❌-Karte, keine Crashes
7. Doc-Link: öffnet SINV-271766 als Formular
8. Reload ohne Kontext: "Kein Kontext" + Link zur Dropzone
9. Format-Drift: Drift-Checkbox aktivierbar, nach Bestätigen läuft
Import durch
10. Gutschrift: 2 Credit-Note-Zeilen → negative DN (−101,95 €) als
eigenes Dokument in Ergebnistabelle
Neue Frappe-Page /app/import-assistant, die den Import-Flow in drei Schritten
durchführt:
1. Konfiguration — zeigt die Analyse-Zusammenfassung aus dem Dropzone-Flow
(Dateiname, erkannter Handler, Zeilen/Kunden/Produkte/Zeitraum) und
bietet Formfelder für Profil, Output-Mode-Override sowie den
„Abweichendes Format akzeptieren"-Toggle (wird nur angezeigt, wenn die
Analyse Format-Drift signalisiert).
2. Import — synchroner Aufruf msp.importers.assistant.process_file, der
einen Supplier Import Run anlegt und den Orchestrator startet.
3. Ergebnis — Status-Hero + Counter (Dokumente/Zeilen/Übersprungen/Fehler)
und eine Tabelle aller erzeugten Sales Invoices / Delivery Notes mit
Kunde, klickbarem Doc-Link, Zeilenanzahl und Netto-Summe. Dazu
Folge-Buttons „Run-Details öffnen" und „Weiteren Import starten".
Der Dropzone-Flow (Phase 3) übergibt den Wizard-Kontext jetzt über
frappe.route_options (file_url, handler_key, display_name, suggested_profile,
format_drift, preview) anstatt auf das Supplier-Import-Run-Formular zu
springen — sauberer Schritt-Übergang ohne URL-Parameter-Akrobatik.
Zusätzlich msp.importers.assistant.run_documents — whitelisted Endpoint,
der pro Run die erzeugten Zieldokumente gruppiert zurückgibt. Frappe's
client.get_list liefert auf Child-Doctypes nur die name-Spalte, daher der
dedizierte SQL-Helper.
End-to-End im Browser validiert:
Dropzone-Upload → Analyse (100 % Confidence) → Assistent öffnet sich
mit vorausgefülltem Profil → Import starten → 57 Dokumente erzeugt
(Mix aus Sales Invoices und Delivery Notes gemäß Customer.billing_mode),
Tabelle zeigt alle mit klickbaren Links.
Fundament für den geplanten Multi-Format-Import-Assistenten. Die bestehende
Parser/Builder-Infrastruktur bleibt unverändert — die neue Handler-Schicht
ist eine dünne Hülle darüber, die zusätzlich ZIP-Entpacken und Sniff-basierte
Auto-Erkennung erledigt.
Neue Bausteine:
- msp.importers.handlers.base.BaseFileHandler mit sniff() / preview() /
execute() und Dataclasses PreviewResult / ExecuteResult.
- msp.importers.handlers.registry mit detect_best_handler() (Scoring aller
registrierten Handler gegen Dateiinhalt + Name, Cutoff bei confidence >= 0.5).
- msp.importers.zip_unpack: entpackt ZIPs mit genau einer CSV in ein Temp-
Verzeichnis (wie ADN sie monatlich liefert).
- msp.importers.handlers.adn_monthly_csv_handler: erster Handler, wrappt den
bestehenden ADN-Parser und Orchestrator. Sniff erkennt ADN-Header mit 100%
Confidence, ZIP-Dateien mit ADN-Namensmuster mit 85% Confidence.
HTTP-Endpoints für die Dropzone-UI (msp.importers.assistant):
- list_handlers(): Metadaten aller Handler — für manuelle Format-Auswahl
in der UI, wenn Auto-Erkennung fehlschlägt.
- analyze_file(file_url): Detection + DB-freie Vorschau — liefert
line_count, customer_count, product_count, period_label, format_drift,
credit_note_lines, suggested_profile.
- process_file(file_url, handler_key, config): führt den Import aus, gibt
Zusammenfassung + Supplier-Import-Run-Name zurück.
Frappe-Page „Lieferanten-Import" (/app/import-dropzone):
- Drag-&-Drop-Zone für CSV und ZIP, Multi-Upload.
- Pro Datei eine reaktive Karte mit Lifecycle
(uploading → analyzing → matched/unmatched/error), die ihre Backend-Calls
selbst steuert (XHR-Upload mit Progress-Bar, anschließend
msp.importers.assistant.analyze_file).
- Bei Auto-Erkennung: Stats + „Assistent öffnen"-Button. Routet aktuell
noch auf die bestehende Supplier-Import-Run-Form mit vorbelegten
Parametern — wird in Phase 4 durch einen eigenen Wizard abgelöst.
- Bei Unmatched: Dropdown mit allen verfügbaren Handlern zur manuellen
Zuordnung.
End-to-End im Browser validiert: CSV-Upload → Karte mit 100%-Confidence-
Erkennung → 116 Zeilen, 57 Kunden, 11 Produkte, Zeitraum 02.2026–02.2027.
ZIP-Upload funktioniert analog (getestet via Smoke-Skript).
Zwei Bugs beim ersten realen Gutschrift-Test (ADN-CSV vom 16.10.2025, 2 Zeilen
mit negativer Menge):
1. Dedup-Fehler — Gutschriftzeilen wurden als „bereits importiert" geskippt,
weil die Prüfung (subscription_external_id, period_start, period_end) auch
bei der regulären Rechnung desselben Monats matchte. Gutschriften sind aber
inhaltlich Korrekturen, keine Dubletten. Fix: Dedup greift nur bei
regulären Rechnungszeilen, nicht bei Gutschriftzeilen (qty<0, amount<0
oder document_type=Gutschrift).
2. Title-Override — ERPNext setzt bei Sales-Invoice-Insert den Title aus der
autoname-Schablone auf ``{customer_name}``, wodurch unser „Abrechnung
Microsoft Abo MM.JJJJ" verloren ging. Fix: nach doc.insert() den Title
direkt per db.set_value wieder einsetzen (umgeht Auto-Title-Logik).
Ergebnis der Validierung (Oktober 2025, das einzige Monat mit Gutschrift-
Zeilen in den verfügbaren CSVs):
- SINV-271450: CUST-21844, -2× MAPID-20763063, Netto -11,76 / Brutto -13,99
- SINV-271455: CUST-21865, -5× ITEM-00052, Netto -123,60 / Brutto -147,08
- Beide mit is_return=1 und korrektem Titel „Gutschrift Microsoft Abo 10.2025"
Nebenbefund zu adnconnect: historische Gutschriftbehandlung dort fehlerhaft —
die -2-Zeile von CUST-21844 landete in der Haupt-SI mit Rabatt=100 %
(Gutschrift-Effekt verloren), die -5-Zeile von CUST-21865 wurde komplett
verschluckt. Unser Importer deckt das jetzt sauber ab.
Bisher hat der Builder nur ``doc.taxes_and_charges = template_name`` gesetzt und
darauf vertraut, dass Frappe die Steuerzeilen aus dem Template auto-populiert.
Das Verhalten war aber nicht zuverlässig: in vielen Fällen entstand eine
taxes-Zeile mit ``rate=0``, wodurch Items ohne eigenes item_tax_template
komplett aus der Steuerberechnung fielen (z. B. Power BI Pro bei CUST-21116:
174,44 € netto ohne MwSt. → 33,14 € fehlende Steuer).
adnconnect löst das mit explizitem Kopieren der Template-Zeilen in die
taxes-Tabelle des SI. Wir spiegeln das Verhalten in
``DocumentBuilder._copy_tax_template_rows`` und rufen es sowohl für Sales
Invoices als auch Delivery Notes auf.
Ergebnis der Validierung:
- 226 / 226 Vergleichsfälle in den Monaten 11.2025–02.2026 sind jetzt
bit-genau deckungsgleich mit adnconnect (vorher 218 / 226).
- Keine verbleibenden Stammdaten-Drift-Differenzen mehr.
Generalisierung des bisher nur in adnconnect existierenden Konzepts „inkludierte
Lizenzen" (über IT-Verträge gedeckte Microsoft-Seats) in einen lieferantenneutralen
DocType im MSP-Modul. Damit wird die Pauschal-Vertrags-Logik unabhängig vom
Distributor (ADN heute, ALSO/Ingram/Pax8 morgen).
Datenmodell:
- License Coverage (parent): customer + optional it_contract / valid_from /
valid_to + enabled-Toggle + freie Notes. Mehrere Coverages pro Kunde sind
zulässig (z. B. mehrere Standorte, parallele Verträge).
- License Coverage Item (child): item-Link (lieferantenagnostisch) + qty.
vendor_product_id wird vom Item gefetched für Plausibilität / Dokumentation.
Builder-Integration (msp.importers.coverage + builder.py):
- Pro Importlauf wird ein CoverageCache aller relevanten Kunden einmal aus der
DB geladen (eine SQL-Abfrage statt N pro Position).
- _append_line splittet jede CanonicalRow bei Bedarf in zwei Sales-Invoice-/
Delivery-Note-Items: min(qty, covered) mit discount_percentage=100 +
„ohne Berechnung"-Hinweis, plus Restmenge zum Listpreis.
- Subscription-Event referenziert die primäre (=erste) Item-Row, das
supply_subscription-Link wird auf allen Item-Rows der kanonischen Zeile
gesetzt.
- Coverage greift nur bei echten Rechnungen, nicht bei Gutschriften.
Migration:
- Patch migrate_adn_included_services_to_license_coverage übernimmt bestehende
ADN Included Services (parent + items) idempotent in License Coverage. Marker
im Notes-Feld verhindert Doppel-Migration. Auf dem Testsystem 5/5 Records
korrekt portiert.
Validation:
- Re-Import der 5 Test-Monate (Nov 2025 – Mär 2026) zeigt: die 5 Coverage-Kunden
produzieren jetzt deckungsgleiche Beträge zu adnconnect (z. B. CUST-21877:
10× Premium berechnet + 11× zum 100%-Rabatt = exakt das Muster von
SINV-269822). Verbleibende Diffs nur noch durch Power-BI-Pro-Tax-Template
auf Testsystem.
Reconciliation-POC erweitert: erp_coverage() liest die License Coverage über
die ERPNext-REST-API; Reconciliation-Tabelle zeigt jetzt Spalten Graph / ERP /
Coverage / Billable mit klassifizierten Status-Hinweisen (z. B. „Coverage > Graph
= Upsell-Chance").
Wichtige Semantik-Korrektur: Der Distributor-Rabatt in ADN-CSVs (z. B.
17,5 %) ist die Wiederverkäufer-Marge von itsdave und darf NICHT an den
Endkunden durchgereicht werden. Der Kunde zahlt den LISTPREIS; itsdave
bezahlt an ADN den EINZELPREIS (= Listpreis × (1 − Rabatt)).
Bisher hat der Builder fälschlich den Einzelpreis als Sales-Invoice-Rate
verwendet — der Kunde hätte dadurch rund 17,5 % zu wenig berechnet
bekommen.
Änderungen:
- builder.py: rate = row.list_price (Fallback: unit_price, amount/qty)
- subscriptions.py: Supply Subscription Event trägt jetzt sowohl
Kundensicht (rate, amount) als auch Einkaufssicht (cost_rate,
cost_amount, reseller_discount_pct).
- supply_subscription_event: neue Sektion „Einkauf & Marge" mit
cost_rate, cost_amount, reseller_discount_pct, margin_amount
(automatisch berechnet: amount − cost_amount).
Diff-Test Feb-2026-CSV:
50 von 57 Kunden nun deckungsgleich mit adnconnect.
7 verbleibende Diffs:
5 echte adnconnect-Bugs (Split-Logik / leere SIs) — in Summe 762 €
Unterabrechnung gegenüber korrektem Listpreis
2 Testsystem-Tax-Template-Drift (Power BI Pro) — kein Code-Problem
Fügt dem MSP-Workspace einen neuen Card-Break „Lieferanten-Import" hinzu
mit Links zu Supplier Import Run, Supplier Import Profile, Supply
Subscription und Supply Subscription Event. Zusätzlich neue Shortcuts
für Lieferanten-Import und Supply Subscriptions.
Damit ist der neue Importer über das reguläre MSP-Desk-Menü erreichbar;
die alte adnconnect-App hat keinen eigenen Workspace-Eintrag und bleibt
weiterhin für Historie-Zugriff installiert, aber nicht mehr als
primärer Einstiegspunkt sichtbar.
Checkt ein vollständiges Profil „ADN Monthly CSV v1" als Fixture ein und
erweitert die fixtures-Konfiguration in hooks.py, damit der Record bei
Bench-Install/-Update idempotent angelegt wird. Damit ist der ADN-Import
nach Installation der MSP-App sofort einsatzbereit, ohne dass das Profil
manuell konfiguriert werden muss.
Die Defaults entsprechen der bisherigen adnconnect-Konfiguration
(itsdave GmbH, MwSt. 19 %, Standard-AGBs, Titelpräfix „Abrechnung
Microsoft Abo ").
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.
Implementiert das eigentliche Import-Framework im Paket msp.importers/:
- base.py: abstrakte BaseSupplierParser-Klasse + CanonicalRow-Dataclass
(lieferantenneutrale Repräsentation einer Rechnungsposition) sowie
Helfer für deutsche Dezimal-/Datums-Parser und Enum-Normalisierung
(Monthly/Annual/Triennial, P1M/P1Y/P3Y, New/Renewal/Billing/Upgrade/
Cancel).
- registry.py: Parser-Registry, über parser_key aufgelöst. Neue
Lieferanten bringen einen eigenen Parser mit und registrieren sich
per @register-Dekorator.
- adn_monthly_csv.py: Parser für ADNs monatliche Rechnungs-CSV.
Extrahiert alle relevanten Felder strukturiert, inklusive
SUBSCRIPTION_ID_EXTERNAL, BILLINGPLAN, VERTRAGSDAUER, WARTUNGSBEGINN/
-ENDE und BUCHUNGSTYP — Daten, die der alte adnconnect-Importer
verworfen oder nur in HTML-Descriptions vergraben hatte.
- resolution.py: Lookups für Customer + Item und die Output-Mode-
Selektion (Run-Override → Customer.billing_mode → Profil-Default).
- subscriptions.py: Upsert-Logik für Supply Subscription + Event,
inklusive Deduplizierung per (subscription_external_id, period).
- builder.py: DocumentBuilder, der sowohl Sales Invoices als auch
Delivery Notes aus kanonischen Zeilen erzeugt. Rechnungspreise werden
per unit_price × qty gesetzt (mit Rabatt in description) — korrekt
gemäß ADN-POSITIONSPREIS statt adnconnects fehlerhafter Listenpreis/
discount_percentage-Kombination.
- run_orchestrator.py: verdrahtet das Ganze und wird aus dem
Supplier-Import-Run-Controller über die Whitelisted-Methode do_import
aufgerufen.
Führt ein lieferantenagnostisches Lizenzmodell ein, das den Weg für den
künftigen Ersatz der adnconnect-App ebnet:
- Supply Subscription: Golden Record pro Subscription-Lifecycle, unique
über die externe Vendor-Subscription-ID (z. B. Microsoft Subscription-ID).
Denormalisierte Felder current_period_end, qty_current, last_event_date
werden aus den Events gepflegt.
- Supply Subscription Event: Eine Episode pro Abrechnungszeitraum, mit
Rück-Links auf Sales Invoice, Delivery Note, Purchase Invoice und
Supplier Import Line.
- Supplier Import Profile / Run / Line: Modulares Import-Framework, in dem
pro Lieferant (ADN, künftig ALSO/Ingram/…) ein Profil mit parser_key +
Defaults konfigurierbar ist. Ein Run bündelt einen konkreten Import
inklusive Kanonik-Zeilen und Statistiken.
- Custom Field „supply_subscription" auf Sales Invoice Item und Delivery
Note Item — das ist das einzige Standard-ERPNext-Feld, das unabhängig
vom Lieferanten hinzukommt. Alle weiteren Attribute werden über den
Link erreichbar.
Die Importer-Logik liegt separat (nächster Commit).
- IT Object: git_remote_url, git_branch, git_path_on_host (visible
only when type = Git Repository)
- Contact custom fields: teams_user_id, is_external_contractor,
contractor_for (via fixtures)
- Remove x509_certificate hooks from hooks.py (moved to DocType controller)
- Add Custom Field fixture export filter to hooks.py
- German translations (de.csv) for all new DocTypes and fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Maps work tracker projects, local directories, chat participants
to customers for automated work time attribution
- Fields: customer, source_type, pattern (glob), employee, note
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
frappe.model.set_value in a loop triggered ERPNext pricing recalculation per item,
causing values to be overwritten. Now sets values on locals objects directly and
triggers a single recalculation at the end.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>