Statistik: uebrige Audit-Korrekturen (Abo-Regeln, Prognose, Sortierung)

A01 — Abo-Regeln waren ueber das Aktiv-Feld umgehbar. Das
Selbstbedienungsformular hat gar keinen Aktiv-Schalter, es spiegelt den Zustand
nur in einem versteckten Feld zurueck; `updateStatus()` hat den Wert trotzdem
uebernommen. Wer den Request manipulierte, konnte sein eigenes Abo dauerhaft
deaktivieren und so die Mindestlaufzeit umgehen, am Pausenpfad mit seinen
Sperren vorbei. Das Feld wirkt jetzt nur noch im Admin-Pfad.

A16 — Statuswechsel wurden nur aus dem Formular protokolliert. Recovery-Zahlung,
Retry, externe Erledigung und die DSGVO-Anonymisierung schreiben `active` direkt
auf das Modell. Das ist heikel, weil die historische Bestandsrueckrechnung ein
Abo verwirft, solange eine protokollierte Deaktivierung nicht durch eine
spaetere protokollierte Reaktivierung aufgehoben ist — ein stiller
`active = true` nimmt es dauerhaft aus allen Folgemonaten.

Der Eintrag liegt deshalb jetzt am Model (`UserAbo::logStatusTransition()`) und
nicht in den einzelnen Diensten: so kann ihn ein kuenftiger Schreibpfad nicht
vergessen, und er entsteht in derselben Transaktion wie die Statusaenderung.
Nebeneffekt: Die DSGVO-Anonymisierung setzt `active = false` ohne `cancel_date`
— bisher fiel ein so abgeschaltetes Abo rueckwirkend aus allen vergangenen
Monaten, jetzt bleibt die Historie korrekt.

A13 — Ein heute faelliges Abo galt ab Mitternacht als erledigt, der
Ausfuehrungsjob laeuft aber erst um 04:00. In diesem Fenster fehlten die Punkte
in der Prognose UND im Ist. Der neue Scope `UserAbo::scopeOpenExecutionUntil()`
zaehlt den heutigen Liefertag mit, solange keine Ausfuehrung angelegt ist —
genau diese Bedingung verhindert die Doppelzaehlung. Alle drei Prognosen nutzen
jetzt denselben Scope statt je einer eigenen Kopie der Datumsregel.

A11 — Der Namens-Tiebreak der Top 3 lief nach dem Abschneiden auf 25
Kandidaten, bei Gleichstand entschied also die Reihenfolge der Abfrage. Die
Auswahlgrenze wird jetzt um alle Gleichstaende an dieser Grenze erweitert, und
Kandidaten ohne Account fallen vorher weg statt die Plaetze zu blockieren. Als
letzte Regel entscheidet die user_id, damit eine gecachte Rangliste
reproduzierbar bleibt.

A14 — Sortiert wurde ueber den angezeigten Zellentext. Unter `en` liefert die
Anwendung `Y-m-d` und `1,000.00`, damit ergab jedes Datum 0 und `1,000.00` wurde
zur 1. Die Zellen tragen jetzt `data-sort-value` mit dem unformatierten Wert;
ohne Attribut gilt weiter der Textweg. Dabei mitgenommen: Umlaute landeten hinter
dem Z (jetzt localeCompare), leere Werte lagen als 0 zwischen den negativen
Stornowerten, und `next_date` wurde doppelt formatiert.

Ausserdem A08 (Prognose auch ohne erreichte Qualifikationsstufe), A09
(Summenzeile im Neupartner-CSV), A10 (Lieferungszahl vorgeladen), A17
(Faelligkeitsgruppen ohne Monatsueberlauf), A18 (Formelinhalte im CSV als Text)
und A19 (fehlgeschlagene Provisionsberechnung ist als solche erkennbar statt
0,00 EUR).

Audit-Befunde A01, A08, A09, A10, A11, A13, A14, A16, A17, A18, A19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-10 11:19:38 +00:00
parent 92344f7080
commit 5a38f4ac3e
31 changed files with 1931 additions and 241 deletions

View file

@ -49,7 +49,13 @@ return [
'payline_target_label' => 'Soll (Punkte pro Zyklus)',
'payline_actual_label' => 'Ist (im Monat abgerechnet)',
'payline_hint' => 'Payline = :lines Ebenen · :members Berater',
'payline_abo_counts' => ':team_abos Teamabos + :customer_abos Kundenabos in der Payline',
'payline_abo_counts' => ':team_abos Teamabos + :customer_abos Kundenabos in der Payline = :total Abos gesamt',
'abo_order_short' => '(Abo)',
'points_order_legend' => 'Bestellungen: blau = eigene Bestellung · grau = Kundenbestellung · türkis mit „(Abo)" = Abo-Lieferung.',
'customer_abo_forecast_title' => 'Punkte aus meinen Kundenabos zum 5. / 10. / 20. / 25.',
'customer_abo_forecast_hint' => 'Kumuliert: die Punkte aller eigenen Kundenabos, deren Lieferung bis zum jeweiligen Termin dieses Monats noch aussteht. Ohne dein eigenes Abo und ohne Team.',
'customer_abo_list_title' => 'Meine Kundenabos (:count)',
'customer_abo_list_search' => 'Kundenabos durchsuchen...',
'payline_none' => 'Noch keine Payline vorhanden (kein Provisionslevel erreicht).',
'abo_points_per_cycle' => 'Punkte pro Lieferung (Bestand)',
'abo_points_billed' => 'abgerechnet (:month)',

View file

@ -49,7 +49,9 @@ return [
'welcome_back' => 'Willkommen zurück',
'your_shop' => 'Dein Shop',
'commission_today' => 'Provision (Stand heute)',
'commission_unavailable' => 'Die Provisionsberechnung ist gerade nicht verfuegbar. Der Wert wird nachgetragen, sobald sie wieder laeuft.',
'payline_forecast_title' => 'Payline-Prognose zum 5. / 10. / 20. / 25.',
'payline_forecast_basis' => 'Basis: Paylinepunkte + persönliches Volumen',
'payline_forecast_hint' => 'Basis: aktueller Kundenumsatz + Teamumsatz (Payline). Dazu kommen kumuliert die bis zum jeweiligen Termin noch anstehenden Abo-Punkte deiner Payline (inkl. deiner eigenen Abos und Kundenabos). Prognose: Pausen, Kündigungen oder fehlgeschlagene Zahlungen können den Wert verändern.',
'forecast_day' => 'Prognose zum :date',
'forecast_upcoming' => 'Abo-Punkte offen',
@ -75,19 +77,24 @@ return [
'team_partner_total' => 'Teampartner gesamt',
'payline_partner_count' => 'Teampartner in der Payline',
'payline_abo_count' => 'Abos in der Payline',
'payline_career_fallback_hint' => 'Gezählt nach den Ebenen deines Karriere-Levels: deine Qualifikation für diesen Monat steht noch aus. Sobald sie feststeht, zählt die Kachel nach den Ebenen der Qualifikationsstufe.',
'level_current' => 'Qualifikation in diesem Monat',
'level_career' => 'Karriere-Level',
'level_next_missing' => 'Für die Qualifikation als :level in diesem Monat fehlen dir noch :points Payline-Punkte.',
'level_next_missing_personal' => 'Zusätzlich fehlen :points Punkte persönliches Volumen.',
'level_not_qualified_yet' => 'Noch nicht qualifiziert',
'level_next_missing_personal_only' => 'Für die Qualifikation als :level in diesem Monat fehlen dir noch :points Punkte persönliches Volumen.',
'level_top_reached' => 'Höchstes Level erreicht.',
'level_hint' => 'Monatliche Qualifikationsstufe aus dem Live-Lauf. Payline-Punkte = Teampunkte deiner bezahlten Ebenen inkl. deines Kundenpunkte-Überschusses.',
'forecast_level_title' => 'Level & Provision laut Prognose (Stand 25.)',
'forecast_unverbindlich' => 'unverbindlich',
'forecast_level_disclaimer' => 'Schätzung auf Basis der heutigen Punkte plus der noch offenen Abo-Lieferungen. Rang-/Strukturänderungen im Team sind nicht berücksichtigt - verbindlich ist erst der Monatsabschluss.',
'forecast_level_needs_qualification' => 'Level- und Provisions-Prognose erscheint, sobald deine Qualifikation für diesen Monat feststeht — die Provisionssätze hängen an der Qualifikationsstufe.',
'forecast_level_reached' => 'Laut Prognose erreichst du das nächste Level :level - sofern dein persönliches Volumen von :volume Punkten erreicht wird.',
'forecast_level_missing' => 'Laut Prognose fehlen zum nächsten Level :level noch :points Payline-Punkte.',
'forecast_level_missing_personal' => 'Zusätzlich fehlen :points Punkte persönliches Volumen.',
'forecast_commission' => 'Voraussichtliche Provision laut Prognose: :amount (auf Basis Level :level).',
'forecast_commission_no_level' => 'Voraussichtliche Provision laut Prognose: :amount. Team-Provision kommt hinzu, sobald die erste Qualifikationsstufe erreicht ist.',
// N6: "Die besten Ergebnisse des Monats" (teamintern)
'top_results_title' => 'Die besten Ergebnisse des Monats (Team)',

View file

@ -175,7 +175,13 @@ return [
'payline_target_label' => 'Target (points per cycle)',
'payline_actual_label' => 'Actual (billed this month)',
'payline_hint' => 'Payline = :lines levels · :members consultants',
'payline_abo_counts' => ':team_abos team subscriptions + :customer_abos customer subscriptions in the payline',
'payline_abo_counts' => ':team_abos team subscriptions + :customer_abos customer subscriptions in the payline = :total subscriptions in total',
'abo_order_short' => '(subscr.)',
'points_order_legend' => 'Orders: blue = your own order · grey = customer order · turquoise with "(subscr.)" = subscription delivery.',
'customer_abo_forecast_title' => 'Points from my customer subscriptions for the 5th / 10th / 20th / 25th',
'customer_abo_forecast_hint' => 'Cumulative: the points of all your own customer subscriptions whose delivery is still due by the respective date this month. Without your own subscription and without your team.',
'customer_abo_list_title' => 'My customer subscriptions (:count)',
'customer_abo_list_search' => 'Search customer subscriptions...',
'payline_none' => 'No payline yet (no commission level reached).',
'abo_points_per_cycle' => 'Points per delivery (current stock)',
'abo_points_billed' => 'billed (:month)',

View file

@ -48,7 +48,9 @@ return [
'welcome_back' => 'welcome back',
'your_shop' => 'your shop',
'commission_today' => 'Commission (as of today)',
'commission_unavailable' => 'The commission calculation is currently unavailable. The value will appear once it runs again.',
'payline_forecast_title' => 'Payline forecast for the 5th / 10th / 20th / 25th',
'payline_forecast_basis' => 'Basis: payline points + personal volume',
'payline_forecast_hint' => 'Basis: current customer turnover + team turnover (payline). Plus, cumulatively, the subscription points of your payline still due up to the respective date (incl. your own subscriptions and customer subscriptions). Forecast: pauses, cancellations or failed payments can change the value.',
'forecast_day' => 'Forecast for :date',
'forecast_upcoming' => 'subscription points due',
@ -75,19 +77,24 @@ return [
'team_partner_total' => 'Team partners total',
'payline_partner_count' => 'Team partners in the payline',
'payline_abo_count' => 'Subscriptions in the payline',
'payline_career_fallback_hint' => 'Counted across the levels of your career rank: your qualification for this month is still pending. Once it is set, the tile counts across the levels of the qualification rank.',
'level_current' => 'Qualification this month',
'level_career' => 'Career level',
'level_next_missing' => 'You still need :points payline points to qualify as :level this month.',
'level_next_missing_personal' => 'Additionally :points points of personal volume are missing.',
'level_not_qualified_yet' => 'Not yet qualified',
'level_next_missing_personal_only' => 'You still need :points points of personal volume to qualify as :level this month.',
'level_top_reached' => 'Highest level reached.',
'level_hint' => 'Monthly qualification level from the live run. Payline points = team points of your paid levels including your customer points surplus.',
'forecast_level_title' => 'Level & commission according to forecast (as of the 25th)',
'forecast_unverbindlich' => 'non-binding',
'forecast_level_disclaimer' => 'Estimate based on today\'s points plus the still open subscription deliveries. Rank/structure changes in the team are not taken into account - only the monthly closing is binding.',
'forecast_level_needs_qualification' => 'The level and commission forecast appears as soon as your qualification for this month is set — the commission rates depend on the qualification rank.',
'forecast_level_reached' => 'According to the forecast you will reach the next level :level - provided your personal volume of :volume points is reached.',
'forecast_level_missing' => 'According to the forecast, :points payline points are still missing for the next level :level.',
'forecast_level_missing_personal' => 'Additionally :points points of personal volume are missing.',
'forecast_commission' => 'Expected commission according to forecast: :amount (based on level :level).',
'forecast_commission_no_level' => 'Projected commission: :amount. Team commission is added once the first qualification level is reached.',
// N6: "Best results of the month" (team-internal)
'top_results_title' => 'Best results of the month (team)',

View file

@ -158,7 +158,13 @@ return [
'payline_target_label' => 'Objetivo (puntos por ciclo)',
'payline_actual_label' => 'Real (facturado este mes)',
'payline_hint' => 'Payline = :lines niveles · :members asesores',
'payline_abo_counts' => ':team_abos suscripciones de asesores + :customer_abos suscripciones de clientes en la payline',
'payline_abo_counts' => ':team_abos suscripciones de equipo + :customer_abos suscripciones de clientes en la payline = :total suscripciones en total',
'abo_order_short' => '(susc.)',
'points_order_legend' => 'Pedidos: azul = pedido propio · gris = pedido de cliente · turquesa con «(susc.)» = entrega de suscripción.',
'customer_abo_forecast_title' => 'Puntos de mis suscripciones de clientes para el 5 / 10 / 20 / 25',
'customer_abo_forecast_hint' => 'Acumulado: los puntos de todas tus suscripciones de clientes cuya entrega aún está pendiente hasta la fecha correspondiente de este mes. Sin tu propia suscripción y sin el equipo.',
'customer_abo_list_title' => 'Mis suscripciones de clientes (:count)',
'customer_abo_list_search' => 'Buscar suscripciones de clientes...',
'payline_none' => 'Aún no hay payline (no se ha alcanzado ningún nivel de comisión).',
'abo_points_per_cycle' => 'Puntos por entrega (cartera actual)',
'abo_points_billed' => 'facturado (:month)',

View file

@ -48,7 +48,9 @@ return [
'welcome_back' => 'bienvenido de nuevo',
'your_shop' => 'tu tienda',
'commission_today' => 'Comisión (a día de hoy)',
'commission_unavailable' => 'El calculo de comisiones no esta disponible en este momento. El valor aparecera cuando vuelva a ejecutarse.',
'payline_forecast_title' => 'Pronóstico de payline para el 5 / 10 / 20 / 25',
'payline_forecast_basis' => 'Base: puntos de payline + volumen personal',
'payline_forecast_hint' => 'Base: volumen de clientes actual + volumen del equipo (payline). Más, de forma acumulada, los puntos de suscripción de tu payline pendientes hasta la fecha correspondiente (incl. tus propias suscripciones y las de tus clientes). Pronóstico: pausas, cancelaciones o pagos fallidos pueden cambiar el valor.',
'forecast_day' => 'Pronóstico para el :date',
'forecast_upcoming' => 'puntos de suscripción pendientes',
@ -75,19 +77,24 @@ return [
'team_partner_total' => 'Socios en total',
'payline_partner_count' => 'Socios en la payline',
'payline_abo_count' => 'Suscripciones en la payline',
'payline_career_fallback_hint' => 'Contado según los niveles de tu nivel de carrera: tu calificación de este mes aún está pendiente. En cuanto esté fijada, la tarjeta contará según los niveles del nivel de calificación.',
'level_current' => 'Calificación de este mes',
'level_career' => 'Nivel de carrera',
'level_next_missing' => 'Para calificar como :level este mes te faltan :points puntos de payline.',
'level_next_missing_personal' => 'Además faltan :points puntos de volumen personal.',
'level_not_qualified_yet' => 'Aún no calificado',
'level_next_missing_personal_only' => 'Para calificar como :level este mes te faltan :points puntos de volumen personal.',
'level_top_reached' => 'Nivel más alto alcanzado.',
'level_hint' => 'Nivel de calificación mensual del cálculo en vivo. Puntos de payline = puntos de equipo de tus niveles pagados incluyendo tu excedente de puntos de clientes.',
'forecast_level_title' => 'Nivel y comisión según la previsión (al día 25)',
'forecast_unverbindlich' => 'sin compromiso',
'forecast_level_disclaimer' => 'Estimación basada en los puntos de hoy más las entregas de suscripción pendientes. No se consideran cambios de rango/estructura en el equipo - solo el cierre mensual es vinculante.',
'forecast_level_needs_qualification' => 'La previsión de nivel y comisión aparece en cuanto se fija tu calificación de este mes: los porcentajes de comisión dependen del nivel de calificación.',
'forecast_level_reached' => 'Según la previsión alcanzarás el siguiente nivel :level - siempre que se alcance tu volumen personal de :volume puntos.',
'forecast_level_missing' => 'Según la previsión faltan :points puntos de payline para el siguiente nivel :level.',
'forecast_level_missing_personal' => 'Además faltan :points puntos de volumen personal.',
'forecast_commission' => 'Comisión prevista según la previsión: :amount (según nivel :level).',
'forecast_commission_no_level' => 'Comision prevista segun el pronostico: :amount. La comision de equipo se anade en cuanto se alcanza el primer nivel de cualificacion.',
// N6: "Los mejores resultados del mes" (interno del equipo)
'top_results_title' => 'Los mejores resultados del mes (equipo)',

View file

@ -49,7 +49,13 @@ return [
'payline_target_label' => 'Objectif (points par cycle)',
'payline_actual_label' => 'Réel (facturé ce mois-ci)',
'payline_hint' => 'Payline = :lines niveaux · :members conseillers',
'payline_abo_counts' => ':team_abos abonnements conseillers + :customer_abos abonnements clients dans la payline',
'payline_abo_counts' => ':team_abos abonnements équipe + :customer_abos abonnements clients dans la payline = :total abonnements au total',
'abo_order_short' => '(abo)',
'points_order_legend' => 'Commandes : bleu = commande personnelle · gris = commande client · turquoise avec « (abo) » = livraison d’abonnement.',
'customer_abo_forecast_title' => 'Points de mes abonnements clients pour le 5 / 10 / 20 / 25',
'customer_abo_forecast_hint' => 'Cumulé : les points de tous tes abonnements clients dont la livraison est encore à venir jusqu’à la date concernée ce mois-ci. Sans ton propre abonnement et sans l’équipe.',
'customer_abo_list_title' => 'Mes abonnements clients (:count)',
'customer_abo_list_search' => 'Rechercher des abonnements clients...',
'payline_none' => 'Pas encore de payline (aucun niveau de commission atteint).',
'abo_points_per_cycle' => 'Points par livraison (portefeuille actuel)',
'abo_points_billed' => 'facturé (:month)',

View file

@ -49,7 +49,9 @@ return [
'welcome_back' => 'Bon retour',
'your_shop' => 'Votre boutique',
'commission_today' => 'Commission (à ce jour)',
'commission_unavailable' => 'Le calcul des commissions n\'est pas disponible actuellement. La valeur apparaitra des qu\'il fonctionnera de nouveau.',
'payline_forecast_title' => 'Prévision payline pour le 5 / 10 / 20 / 25',
'payline_forecast_basis' => 'Base : points payline + volume personnel',
'payline_forecast_hint' => 'Base : chiffre d’affaires clients actuel + chiffre d’affaires équipe (payline). S’y ajoutent, de manière cumulée, les points d’abonnement de ta payline encore à venir jusqu’à la date concernée (y compris tes propres abonnements et abonnements clients). Prévision : pauses, résiliations ou paiements échoués peuvent modifier la valeur.',
'forecast_day' => 'Prévision pour le :date',
'forecast_upcoming' => 'points d’abonnement à venir',
@ -75,19 +77,24 @@ return [
'team_partner_total' => 'Partenaires au total',
'payline_partner_count' => 'Partenaires dans la payline',
'payline_abo_count' => 'Abonnements dans la payline',
'payline_career_fallback_hint' => 'Compté sur les niveaux de ton niveau de carrière : ta qualification pour ce mois n\'est pas encore établie. Dès qu\'elle le sera, la tuile comptera sur les niveaux du niveau de qualification.',
'level_current' => 'Qualification de ce mois',
'level_career' => 'Niveau de carrière',
'level_next_missing' => 'Pour te qualifier comme :level ce mois-ci, il te manque encore :points points de payline.',
'level_next_missing_personal' => 'Il manque en plus :points points de volume personnel.',
'level_not_qualified_yet' => 'Pas encore qualifié',
'level_next_missing_personal_only' => 'Pour te qualifier comme :level ce mois-ci, il te manque encore :points points de volume personnel.',
'level_top_reached' => 'Niveau le plus élevé atteint.',
'level_hint' => 'Niveau de qualification mensuel issu du calcul en direct. Points de payline = points d’équipe de tes niveaux payés, y compris ton excédent de points clients.',
'forecast_level_title' => 'Niveau et commission selon la prévision (au 25)',
'forecast_unverbindlich' => 'sans engagement',
'forecast_level_disclaimer' => 'Estimation basée sur les points actuels plus les livraisons d’abonnement encore ouvertes. Les changements de rang/structure dans l’équipe ne sont pas pris en compte - seule la clôture mensuelle fait foi.',
'forecast_level_needs_qualification' => 'La prévision de niveau et de commission apparaît dès que ta qualification pour ce mois est établie : les taux de commission dépendent du niveau de qualification.',
'forecast_level_reached' => 'Selon la prévision, tu atteindras le niveau suivant :level - à condition que ton volume personnel de :volume points soit atteint.',
'forecast_level_missing' => 'Selon la prévision, il manque encore :points points de payline pour le niveau suivant :level.',
'forecast_level_missing_personal' => 'Il manque en plus :points points de volume personnel.',
'forecast_commission' => 'Commission prévisionnelle selon la prévision : :amount (sur la base du niveau :level).',
'forecast_commission_no_level' => 'Commission prevue selon la prevision : :amount. La commission d\'equipe s\'ajoute des que le premier niveau de qualification est atteint.',
// N6 : « Les meilleurs résultats du mois » (interne à l'équipe)
'top_results_title' => 'Les meilleurs résultats du mois (équipe)',