Preise zusammenfassen¶
Gleiche Preise auf vielen Gerätetypen zu einer Preiskategorie zusammenfassen, damit eine Preisänderung eine Zeile trifft statt vierzig.
Route: /price/migration · Recht: price_view zum Lesen des Plans,
price_edit zum Übernehmen
Wozu¶
Eine Preiszeile gilt für genau einen Gerätetyp. Steht derselbe Betrag auf vierzig Typen, sind neununddreißig davon Kopien. Das fällt nicht auf, solange sich nichts ändert — und wird teuer, sobald sich etwas ändert: jede Preisrunde, jede Korrektur und jede neue Gültigkeit muss alle vierzig einzeln treffen.
Die Preiskategorie ist der Ort, an dem so eine Sorte einmal steht. Diese Seite findet die Sorten im Bestand, legt die Kategorien an und hebt den Betrag hoch.
Woher der Anlass kommt
Die Preis-Übersicht (/price/overview) zeigt unter „Größte Kopiencluster",
wie viele Beträge mehrfach vergeben sind und wie viele Gerätetypen daran
hängen. Der Knopf Zusammenfassen dort führt hierher.
Schritt 1: Woran die Zusammenfassung erkannt wird¶
Die Seite liest ein Zusatzfeld mit der Artikelnummer auf den Preiszeilen.
Vorgabe ist Artikelnummer; heißt das Feld in eurer Installation anders,
steht der Name oben im Eingabefeld.
Der Gedanke dahinter: die dreistufige Nummer (100.110.01) ist die
maschinenlesbare Spur einer Klassenbildung, die im Bestand längst von Hand
gemacht wurde. Gleiche Nummer heißt „dieselbe Sorte Gerät, derselbe Aufwand".
Wer die Kategorien von Hand nachbaut, tippt ab, was schon da ist.
Schritt 2: Was im Bestand steht¶
Das Lesen des Plans schreibt nichts. Es beantwortet die Frage, die vorher niemand beantworten kann: sind die Nummern sauber genug, um Kategorien daraus zu bilden?
Die Tabelle zeigt je Nummer:
| Spalte | Bedeutung |
|---|---|
| Nummer | der Wert des Zusatzfelds, unverändert |
| Zeilen | Preiszeilen mit dieser Nummer |
| Gerätetypen | wie viele Typen darunter fallen, mit Beispielnamen |
| Beträge | wie viele Betrags-Slots übernehmbar sind und wie viele im Konflikt |
| Künftige Zeilen | Zeilen mit späterer Gültigkeit; sie bleiben unangetastet |
| Hinweis | warum eine Gruppe nicht übernommen wird |
Zwei Hinweise kommen vor:
- nur ein Gerätetyp — eine Nummer an einer einzigen Zeile ist keine Klassenbildung. Eine Kategorie daraus zu machen tauschte eine Preiszeile gegen eine Kategorie plus eine Preiszeile plus eine Mitgliedschaft, also mehr Pflege für dieselbe Aussage. Die Kategorie lohnt ab dem zweiten Gerätetyp.
- Kategorie existiert bereits — es gibt schon eine Kategorie mit dieser Nummer, typisch aus einem früheren Lauf. Eine zweite entsteht nicht, ihre Beträge lassen sich aber weiterhin nachziehen.
Betrags-Slots¶
Übernommen wird nicht „ein Preis je Nummer", sondern ein Preis je Slot: Preisgruppe × Kalibrierart. Die Preisfindung filtert beides exakt, also braucht jede im Bestand belegte Kombination ihre eigene Kategoriezeile — sonst fiele genau diese Kombination nach der Übernahme auf „kein Preis".
Schritt 3: Konflikte entscheiden¶
Ein Slot ist im Konflikt, wenn seine aktuell gültigen Zeilen verschiedene Beträge tragen. Welcher davon der Kategoriepreis wird, ist eine fachliche Frage; die Anwendung entscheidet sie nicht.
Die Seite zeigt je Konflikt die vorkommenden Beträge mit der Zahl der Zeilen, die jeden tragen — häufigster zuerst. Das ist eine Auskunft, keine Vorauswahl.
Was eine Entscheidung tut, und was nicht
Der gewählte Betrag löst die Kopien auf, die genau diesen Betrag tragen. Die abweichenden Zeilen bleiben als Typzeilen stehen und sind danach genau das, was sie immer waren: Ausnahmen. Eine Entscheidung schreibt also keine Preise um.
Offen gelassene Konflikte werden nicht übernommen. Ihre Zeilen bleiben unverändert, und der Schritt lässt sich später wiederholen.
Steuersatz, Einheit, Menge und Rabatt der Kategoriezeile stammen aus einer Zeile des Slots, die den gewählten Betrag trägt. Entschieden wird der Betrag, nicht der ganze Satz.
Schritt 4: Übernehmen¶
Zwei Haken steuern, wie weit die Übernahme geht:
Kategoriebeträge übernehmen. Ohne diesen Haken entstehen nur die Kategorien samt ihren Gerätetypen; die Preise bleiben, wo sie sind. Das ist ein brauchbarer erster Lauf, wenn erst die Zuordnung stehen soll. Die Beträge lassen sich jederzeit nachziehen.
Abgelöste Typzeilen stilllegen. Setzt bei den Kopien das Enddatum auf gestern. Der Haken ist nur zusammen mit den Beträgen wählbar: ohne Kategoriebetrag deckt nichts die stillgelegten Zeilen ab, und der Slot fiele auf „kein Preis".
Gelöscht wird nichts
Stilllegen heißt Enddatum, nicht Löschen. Die Zeilen bleiben lesbar, die Historie bleibt vollständig, und der Schritt ist rückrollbar. Stillgelegt wird außerdem nur, was der Kategoriebetrag wirklich deckt — Zeilen mit abweichendem Betrag, Typen in einer anderen Kategorie und Zeilen, deren Gültigkeit erst heute beginnt, bleiben stehen.
Nach dem Lauf steht in einem Satz, was passiert ist und was ausgelassen wurde: übersprungene Gerätetypen, offen gebliebene Konflikte, bereits vorhandene Kategoriebeträge, stehen gelassene Typzeilen.
Was danach anders ist¶
- Die Preisliste zeigt einen Kategoriebaum statt einer langen Liste einzelner Typpreise; Ausnahmen stehen eingerückt unter ihrer Kategorie.
- Eine Preisänderung an einer zusammengefassten Sorte ist eine Änderung an einer Zeile, zu finden auf der Detailseite der Preiskategorie.
- Die Reiterliste Preiszeilen wird das, wofür sie da ist: Nachschlagen, Korrigieren und Ausnahmen.
Auf der Konsole¶
Derselbe Weg läuft auch als Kommando, ohne Konfliktentscheidungen:
# Bericht, schreibt nichts
docker exec calserver-api-v2 php artisan price-classes:derive
# Kategorien und Mitglieder anlegen
docker exec calserver-api-v2 php artisan price-classes:derive --apply --force
# ... zusätzlich die Kategoriebeträge (nur konfliktfreie Slots)
docker exec calserver-api-v2 php artisan price-classes:derive --apply --amounts --force
# ... und die abgelösten Typzeilen stilllegen
docker exec calserver-api-v2 php artisan price-classes:derive --apply --amounts --retire --force
Konflikte übernimmt das Kommando nie — dafür ist die Seite da.
Die andere Achse: Zeilen mit Kalibrierart als Zuschlag¶
Die Zusammenfassung oben bündelt die Geräteachse (viele Typen, ein Preis). Daneben steht dieselbe Verdopplung auf der Kalibrierart-Achse: Wo für „DAkkS kostet mehr als ISO" früher nur eine eigene Preiszeile je Kalibrierart zur Verfügung stand, führt ein Labor mit 800 Gerätetypen und drei Kalibrierarten 2400 Zeilen — von denen 1600 dieselbe Aussage wiederholen: „plus 30 Prozent".
Ein Kommando misst das nach und fasst es zusammen:
# Bericht: welche Kalibrierarten gibt es, wieviel Prozent liegen ihre Zeilen
# über dem Grundpreis, und wie einig sind sie sich. Schreibt nichts.
docker exec calserver-api-v2 php artisan prices:complexity-to-surcharge
# Je Kalibrierart einen Zuschlag mit dem gemessenen Prozentsatz anlegen
docker exec calserver-api-v2 php artisan prices:complexity-to-surcharge --apply --force
# ... und die Zeilen stilllegen, die der Zuschlag nachrechnet
docker exec calserver-api-v2 php artisan prices:complexity-to-surcharge --apply --retire --force
Drei Dinge, die den Schritt ungefährlich machen:
- Der Vorschlag ist gemessen, nicht gemittelt. Liegen 400 Typen auf +30 % und drei auf +25 %, lautet die Antwort „+30 %, und die drei bleiben" — nicht ein Mittelwert, der in keiner Zeile steht und alle 403 ändert.
- Stillgelegt wird nur, was der Zuschlag exakt nachrechnet. Gerechnet wird mit derselben Methode wie im Betrieb. Alles andere bleibt stehen — und eine gepflegte Zeile mit Kalibrierart schlägt den Zuschlag weiterhin.
- Gelöscht wird nichts. Eine stillgelegte Zeile bekommt ein Enddatum und bleibt für Stichtage in der Vergangenheit gültig.
Einen eigenen Satz setzt man mit --complexity='DAkkS' --percent=30.
Ein anderer Weg: mitgebrachte Preisliste¶
Liegt die Klassenbildung nicht in den Nummern, sondern in einer Tabelle, liest
price-classes:import sie samt Beträgen ein (CSV oder Excel, Pflichtspalten
Nummer und Preiskategorie). Details in
preisliste.md.