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>