mivita/app/Console/Commands/RepairDecimalPoints.php
Kevin a21bebc932 Punkte: Dezimalpunkte in Einmalprodukten, Homeparty und Shop-API nicht mehr abschneiden
Produkte mit 7,5 Punkten wurden an drei Stellen zu ganzen Zahlen:
user_abo_one_time_items (PHP-Cast int, 7,5 -> 7), homeparty_user_order_items
(MySQL rundete auf 8) und shopping_collect_orders (Summe der Shop-API).
Die Migration zieht die drei Spalten auf DECIMAL(10,2) nach (Nachtrag zu
2026_01_22), Cast und Abo-Snapshot rechnen mit float.

points:report-decimal-diff wertet die Differenzen je Berater und Monat aus
(nur lesend), points:repair-decimal korrigiert den laufenden Monat und
zieht die Monatssummen nach.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:42:27 +00:00

103 lines
4.3 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\DecimalPointsRepairService;
use Illuminate\Console\Command;
/**
* Repair: Dezimal-Punkte (z.B. 7,5) im laufenden Provisionsmonat nachziehen.
*
* Hintergrund siehe {@see DecimalPointsRepairService}. Standard ist ein
* Trockenlauf -- geschrieben wird nur mit --force.
*/
class RepairDecimalPoints extends Command
{
protected $signature = 'points:repair-decimal
{--month= : Monat (Standard: aktueller Monat)}
{--year= : Jahr (Standard: aktuelles Jahr)}
{--force : Aenderungen wirklich schreiben (ohne: Trockenlauf)}
{--allow-closed-month : Auch einen abgeschlossenen Monat korrigieren}';
protected $description = 'Abgeschnittene/gerundete Dezimal-Punkte im laufenden Monat korrigieren und Monatssummen nachziehen';
public function handle(DecimalPointsRepairService $service): int
{
$month = (int) ($this->option('month') ?: now()->month);
$year = (int) ($this->option('year') ?: now()->year);
$dryRun = ! $this->option('force');
$isCurrentMonth = $month === (int) now()->month && $year === (int) now()->year;
if (! $dryRun && ! $isCurrentMonth && ! $this->option('allow-closed-month')) {
$this->error("Korrektur in {$month}/{$year} abgelehnt: das ist nicht der laufende Monat.");
$this->line('Abgeschlossene Provisionsmonate bleiben so, wie sie abgerechnet wurden.');
$this->line('Nur wenn das wirklich gewollt ist: --allow-closed-month');
return self::FAILURE;
}
$this->info("Punkte-Repair für {$month}/{$year}".($dryRun ? ' (TROCKENLAUF — es wird nichts geschrieben)' : ''));
$result = $service->repair($month, $year, $dryRun);
if ($result['orders'] === []) {
$this->info('Keine abweichenden Punkte gefunden. Nichts zu tun.');
return self::SUCCESS;
}
if ($result['addon_items'] !== []) {
$this->newLine();
$this->info('Abo-Einmalartikel (abgeschnitten):');
$this->table(
['Order', 'Position', 'Produkt', 'von', 'auf', 'Menge', 'Delta'],
array_map(fn (array $row) => [
$row['order_id'],
$row['item_id'],
mb_strimwidth($row['product'], 0, 40, '…'),
number_format($row['from'], 2, ',', '.'),
number_format($row['to'], 2, ',', '.'),
$row['qty'],
number_format($row['delta'], 2, ',', '.'),
], $result['addon_items'])
);
}
if ($result['collect_orders'] !== []) {
$this->newLine();
$this->info('Sammelbestellungen (gerundet):');
$this->table(
['Order', 'Sammel-ID', 'von', 'auf', 'Delta'],
array_map(fn (array $row) => [
$row['order_id'],
$row['collect_id'],
number_format($row['from'], 2, ',', '.'),
number_format($row['to'], 2, ',', '.'),
number_format($row['delta'], 2, ',', '.'),
], $result['collect_orders'])
);
}
$this->newLine();
$this->info('Zusammenfassung:');
$this->line(' Korrigierte Positionen: '.count($result['addon_items']));
$this->line(' Korrigierte Sammelbest.: '.count($result['collect_orders']));
$this->line(' Betroffene Bestellungen: '.count($result['orders']));
$this->line(' Betroffene Umsatz-Zeilen: '.count($result['sales_volumes']));
$this->line(' Betroffene Berater: '.count($result['users_recalculated']));
$this->line(' Punkte-Differenz gesamt: '.number_format($result['total_delta'], 2, ',', '.'));
if ($dryRun) {
$this->newLine();
$this->warn('Trockenlauf — es wurde nichts geschrieben. Zum Schreiben: --force');
return self::SUCCESS;
}
$this->newLine();
$this->info('Geschrieben. Monatssummen für '.count($result['users_recalculated']).' Berater neu berechnet.');
$this->line('Bereits ausgestellte Rechnungen/Lieferscheine bleiben unverändert (PDFs im Storage).');
return self::SUCCESS;
}
}