mivita/app/Console/Commands/AboMoveOrders.php
Kevin f6faad9ae7 Abo: extern-erledigt für gelöschte Abos + Lieferhistorie-Umzug (Ticket Kunz Mai-Punkte)
- AboExternalCompletionService: Bestellungen gelöschter/ersetzter Abos können
  nachträglich extern erledigt werden (Punkte + Status 8 + Versandstatus);
  das gelöschte Abo wird dabei nicht reaktiviert
- IncentiveTracker::trackSalesVolume: Abo-Zuordnung inkl. gelöschter Abos
  (withTrashed) - nachgebuchte Umsätze bereits getrackter Abos zählen weiter
- Neue Wartungs-Commands: abo:extern-done {shopping_order_id} und
  abo:move-orders {from_abo} {to_abo} (--dry-run, Guards: gleicher Kunde)
- Latent datumsabhängigen Alt-Test in AboExternalCompletionTest repariert
  (Buchung nutzt date(), nicht Carbon::setTestNow)

Fall Kunz (dev/2026-08-12-intensiv-tickets): Dannenhauers gelieferte, aber am
17.06. beim Nachtragen vergessene Mai-Order 53274 → abo:extern-done (+54 P);
Bockhackers Lieferhistorie von gelöschtem Abo 197 → 496 umgehängt (Zähler 4).
Bockhackers 24 P existieren bereits (Juni-Buchung der Mai-Lieferung).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 12:06:29 +00:00

80 lines
2.8 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\UserAbo;
use App\Models\UserAboOrder;
use Illuminate\Console\Command;
/**
* Wartungs-Command: Lieferhistorie (user_abo_orders) eines geloeschten/ersetzten
* Abos auf das Nachfolge-Abo desselben Kunden umhaengen.
*
* Hintergrund: Muss ein Abo geloescht und neu angelegt werden (z. B. Wechsel des
* Zahlungsmittels), startet der Lieferzaehler des neuen Abos bei 0 - Mindestlaufzeit
* und "x-te Lieferung" gehen verloren. Dieses Command uebertraegt die Historie.
*/
class AboMoveOrders extends Command
{
protected $signature = 'abo:move-orders
{from_abo : ID des alten (i. d. R. gelöschten) Abos}
{to_abo : ID des Nachfolge-Abos}
{--dry-run : Nur anzeigen, nichts ändern}';
protected $description = 'Lieferhistorie (user_abo_orders) eines ersetzten Abos auf das Nachfolge-Abo umhängen';
public function handle(): int
{
$from = UserAbo::withTrashed()->find($this->argument('from_abo'));
$to = UserAbo::find($this->argument('to_abo'));
if (! $from || ! $to) {
$this->error('Abo nicht gefunden (from: '.($from ? 'ok' : 'fehlt').', to: '.($to ? 'ok' : 'fehlt').').');
return self::FAILURE;
}
if ($from->id === $to->id) {
$this->error('from_abo und to_abo sind identisch.');
return self::FAILURE;
}
$sameCustomer = strcasecmp(trim((string) $from->email), trim((string) $to->email)) === 0
&& (int) $from->member_id === (int) $to->member_id
&& $from->is_for === $to->is_for;
if (! $sameCustomer) {
$this->error("Abos gehören nicht zum selben Kunden (from: {$from->email} / Berater {$from->member_id}, to: {$to->email} / Berater {$to->member_id}). Abbruch.");
return self::FAILURE;
}
$orders = UserAboOrder::where('user_abo_id', $from->id)->orderBy('id')->get();
if ($orders->isEmpty()) {
$this->info("Keine user_abo_orders bei Abo {$from->id} - nichts zu tun.");
return self::SUCCESS;
}
$this->info("Abo {$from->id} ({$from->email}".($from->trashed() ? ', gelöscht' : '').") → Abo {$to->id}: {$orders->count()} Lieferung(en)");
$this->table(
['user_abo_order', 'shopping_order', 'Status', 'bezahlt'],
$orders->map(fn (UserAboOrder $o) => [$o->id, $o->shopping_order_id, $o->status, $o->paid ? 'ja' : 'nein'])->all()
);
if ($this->option('dry-run')) {
$this->info('DRY-RUN - nichts geändert.');
return self::SUCCESS;
}
UserAboOrder::where('user_abo_id', $from->id)->update(['user_abo_id' => $to->id]);
$to->refresh();
$this->info("Umgehängt. Abo {$to->id} zählt jetzt {$to->getCountPaidOrders()} ausgeführte Lieferung(en).");
return self::SUCCESS;
}
}