Markus Michalski FAQ

Kleinunternehmer: Technische Details

Sylius-Plugin Kleinunternehmer §19 UStG: Architektur, Plugin-Struktur, Tabellen und Migrationen, Admin-Routen und Hooks, Gutschrift-Compiler-Pass sowie ersetzbare Services.

English Version

Zurück zur Übersicht

Kleinunternehmer: Technische Details

Live Demo: Teste dieses und weitere Plugins live — ohne Installation, ohne Risiko: demo.markus-michalski.net

Architektur

graph TD
    A["KleinunternehmerSettings (Singleton)"] --> B["NoticeApplicabilityChecker"]
    C["ZeroTaxCategoryProvider"] --> D["ProductPriceMigrationService"]
    D --> E["MigratedProductVariant (Tabelle der migrierten Varianten)"]
    B --> F["KleinunternehmerExtension (Twig)"]
    B --> G["ShopNoticeNormalizer (Shop API)"]
    F --> H["Twig Hooks: Storefront"]
    F --> I["credit_memo.html.twig (Refund-Plugin)"]
    J["Admin: Einstellungsseite"] --> A
    J --> D
    K["Konsole: migrate-prices"] --> D

Eine Klasse entscheidet, ob der Hinweis gilt: NoticeApplicabilityChecker. Storefront-Twig, Shop API und Gutschrift-PDF fragen alle sie, deshalb stimmen die drei Wege immer überein.

Plugin-Struktur

src/
├── Command/              # MigrateProductPricesCommand, SetupCommand
├── Controller/Admin/     # KleinunternehmerSettingsController, MigrateProductPricesAction
├── DependencyInjection/  # Extension, Configuration, Compiler/CreditMemoPdfTemplatePass
├── Entity/               # KleinunternehmerSettings, MigratedProductVariant
├── Form/Type/            # KleinunternehmerSettingsType
├── Menu/                 # AdminMenuListener, MenuGroup
├── Migrations/           # drei Doctrine-Migrationen
├── Provider/             # ZeroTaxCategoryProvider
├── Repository/           # Repositories für beide Entities
├── Serializer/           # ShopNoticeNormalizer
├── Service/              # NoticeApplicabilityChecker, ProductPriceMigrationService
└── Twig/                 # KleinunternehmerExtension
config/
├── routes/               # admin.yaml (shop.yaml ist leer)
├── services/             # Service-Definitionen
└── twig_hooks/           # admin_settings.yaml, shop_product_prices.yaml
templates/
├── admin/kleinunternehmer/   # Einstellungsseite, Migrationskarte
├── shop/                     # Hinweis-Templates (Absatz und Tabellenzeile)
└── refund/                   # credit_memo.html.twig
translations/             # messages und flashes, Deutsch und Englisch

Das Plugin definiert keine konfigurierbaren Schlüssel (Configuration ist leer) und liest keine Umgebungsvariablen.

Datenbank

erDiagram
    mmd_kleinunternehmer_settings {
        integer id PK
        boolean active
        text noticeTextDe
        text noticeTextEn
    }
    mmd_kleinunternehmer_migrated_variant {
        integer id PK
        string productVariantCode UK
        string originalTaxCategoryCode
        datetime_immutable migratedAt
    }
Tabelle Zweck
mmd_kleinunternehmer_settings Genau eine Zeile mit id 1: Schalter und optionale Hinweistexte (je maximal 1000 Zeichen). Ohne Zeile liefert das Plugin Standardwerte, ohne sie zu speichern
mmd_kleinunternehmer_migrated_variant Eine Zeile je aktuell migrierter Variante mit ursprünglicher Steuerkategorie, damit --restore die richtige Kategorie zurückbringt

Die Entities sind einfache Doctrine-Entities mit PHP-Attributen, keine Sylius-Ressourcen. Es gibt deshalb kein Grid und keine CRUD-Routen.

Drei Migrationen unter dem Namensraum Mmd\SyliusKleinunternehmerPlugin\Migrations:

Version Inhalt
Version20260828121602 Tabelle mmd_kleinunternehmer_settings
Version20260828132209 Tabelle mmd_kleinunternehmer_migrated_variant mit eindeutigem Index auf productVariantCode
Version20260829160426 Spalten noticeTextDe und noticeTextEn

Die Migrationen nutzen die Schema-API von Doctrine statt rohem SQL, damit sie nicht an einer Datenbankplattform hängen. Der Namensraum muss in der Host-Anwendung eindeutig bleiben.

Wie das Bundle sich einhängt

Admin-Hooks

Die Einstellungsseite rendert unter dem Hook-Präfix sylius_admin.mmd_kleinunternehmer_settings.index und erbt das Grundgerüst (Sidebar, Navbar, Kopfzeile) von sylius_admin.common.index. Die Hookables in config/twig_hooks/admin_settings.yaml sind activation_error (Priorität 110), form (100), additional_languages_hint (75) und migration (50) im Inhaltsbereich sowie title (100) und actions (0) im Titelblock.

Routen

Route Pfad Methoden
mmd_sylius_kleinunternehmer_admin_settings /<admin>/kleinunternehmer/settings GET, POST
mmd_sylius_kleinunternehmer_admin_migrate /<admin>/kleinunternehmer/migrate POST

Steuerkategorie und Zone

Die Konstanten für Kategorie und Steuersatz sind kleinunternehmer_zero und kleinunternehmer_zero_rate. Die Zone DE ist fest im Code verankert. Das ist eine bewusste Vereinfachung, die Zone wird nicht dynamisch abgeleitet.

Erweiterbarkeit

Das Plugin definiert keine eigenen Events, Service-Tags oder Interfaces für Dritte. Über die normale Symfony-Service-Dekoration lassen sich drei öffentliche Services ersetzen:

Interface Standard-Implementierung Aufgabe
ZeroTaxCategoryProviderInterface ZeroTaxCategoryProvider 0-%-Kategorie und Steuersatz anlegen
ProductPriceMigrationServiceInterface ProductPriceMigrationService Preise migrieren und zurücksetzen
NoticeApplicabilityCheckerInterface NoticeApplicabilityChecker Entscheidet, ob der Hinweis gilt

Die Methoden im Überblick:

Interface Methoden
NoticeApplicabilityCheckerInterface applies(?order, ?variant, ?product), appliesToTaxTotal(int)
ProductPriceMigrationServiceInterface migrateToSmallBusiness(?callable), restoreFromSmallBusiness(?callable), countPendingMigration(), countMigrated()
ZeroTaxCategoryProviderInterface getOrCreate()

Zusätzlich liefert KleinunternehmerSettingsRepository::getSettings() den Einstellungs-Datensatz. Fehlt die Zeile, gibt es einen nicht gespeicherten Standardwert zurück, damit ein Seitenaufruf in der Storefront nie schreibt.

Ersetzt du NoticeApplicabilityChecker, gilt deine Entscheidung für Storefront, Shop API und Gutschrift-PDF gleichermaßen. Ansonsten hängt sich das Plugin ohne Besonderheit ein: AdminMenuListener hört auf das Sylius-Event sylius.menu.admin.main, und ShopNoticeNormalizer ist ein normaler Serializer-Normalizer mit Priorität 60.

Versionsanzeige

Unter dem Titel der Einstellungsseite steht die installierte Version (kleinunternehmer_plugin_version(), liest Composer\InstalledVersions). Bei einem getaggten Release ist das die Versionsnummer, bei einer lokalen Pfad-Installation dev-main plus die ersten sieben Zeichen des Commit-Hashes.