mivita/app/Http/Controllers/Admin/AboController.php
Kevin 4709dfde55 Abo: Bestellung extern als erledigt markieren (ohne Rechnung/Inkasso)
Neue Admin-Funktion fuer angehaltene Abo-Bestellungen, die der Kunde
ausserhalb des Systems geliefert und verrechnet hat.

Aktion (AboExternalCompletionService):
- Punkte gutschreiben wie bei eingegangener Zahlung (SalesPointsVolume,
  idempotent gegen Doppelbuchung)
- UserAboOrder auf neuen gruenen Status 8 "extern erledigt"; paid bleibt false
- Bestellung: shipped=2 (Lieferung bestaetigt), keine Rechnung/kein Lieferschein
- Blockade aufheben: Abo zurueck auf status=2 (okay), aktiv, next_date neu,
  damit der naechste Termin regulaer laeuft
- wie bei regulaerer Ausfuehrung: IncentiveTracker + AboOneTimeService-Purge

Einstiegspunkte: Button + Bestaetigungs-Modal auf der Kunden-Bestelldetailseite
(SalesController) und auf der Abo-Detailseite (Admin\AboController@externDone,
Route admin_abos_extern_done). Status 8 (gruen) in UserAbo/UserAboOrder, Labels
de/en.

Tests: tests/Feature/AboExternalCompletionTest (Happy Path, Idempotenz,
bezahlt-/Nicht-Abo-Guards, Abo-Detail-Einstieg) - 5 gruen.

Zusaetzlich: docs/2026-06-11-test-suite-vorbestehende-fehler.md - Diagnose der
93 vorbestehenden (von master geerbten) Test-Suite-Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 08:11:45 +00:00

196 lines
7.1 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\UserAbo;
use App\Repositories\AboRepository;
use App\Services\AboExternalCompletionService;
use App\Services\AboItemHistoryService;
use App\Services\AboOrderCart;
use App\Services\AboRetryPaymentService;
use App\Services\Shop;
use Request;
class AboController extends Controller
{
protected $aboRepository;
public function __construct(AboRepository $aboRepository)
{
$this->middleware('admin');
$this->aboRepository = $aboRepository;
}
public function index()
{
if (Request::get('reset') === 'filter') {
set_user_attr('filter_user_shop_id', null);
set_user_attr('filter_status', null);
set_user_attr('filter_member_id', null);
return redirect(route('admin_sales_customers'));
}
// $filter_user_shops = UserAbo::join('user_shops', 'user_shop_id', '=', 'user_shops.id')->orderBy('slug')->get()->pluck('slug', 'id')->unique()->toArray();
$filter_members = UserAbo::join('users', 'user_id', '=', 'users.id')->groupBy('user_id')->join('user_accounts', 'account_id', '=', 'user_accounts.id')->select('users.id', 'users.email', 'user_accounts.first_name', 'user_accounts.last_name')->get();
$data = [
// 'filter_user_shops' => $filter_user_shops,
'filter_members' => $filter_members,
];
return view('admin.abo.index', $data);
}
public function detail($id)
{
$data = Request::all();
$user_abo = UserAbo::findOrFail($id);
// init Yard
AboOrderCart::initYard($user_abo);
$customer_detail = AboOrderCart::getCustomerDetail();
AboOrderCart::makeOrderYard($user_abo);
$comp_products = [];
if ($user_abo->is_for === 'me') {
$comp_products = Shop::getCompProducts('abo-me');
}
$data = [
'user_abo' => $user_abo,
'isAdmin' => true,
'customer_detail' => $customer_detail,
'view' => $user_abo->is_for,
'comp_products' => $comp_products,
];
return view('admin.abo.detail', $data);
}
public function update($id)
{
$data = Request::all();
if (isset($data['action'])) {
if ($data['action'] === 'abo_update_settings') {
$user_abo = UserAbo::findOrFail($data['id']);
$this->aboRepository->setModel($user_abo);
$this->aboRepository->update($data);
return redirect(route('admin_abos_detail', [$id]));
}
}
}
public function rollback($id)
{
$user_abo = UserAbo::findOrFail($id);
AboOrderCart::initYard($user_abo);
$success = AboItemHistoryService::rollbackToInitial($user_abo);
if ($success) {
$user_abo->refresh();
AboOrderCart::makeOrderYard($user_abo);
AboOrderCart::checkNumOfCompProducts($user_abo);
\Session()->flash('alert-success', __('abo_history.rollback_success'));
} else {
\Session()->flash('alert-error', __('abo_history.rollback_no_data'));
}
return redirect(route('admin_abos_detail', [$id]));
}
public function retryPayment($id, AboRetryPaymentService $retryPaymentService)
{
$user_abo = UserAbo::findOrFail($id);
$result = $retryPaymentService->retry($user_abo);
\Session()->flash($result['success'] ? 'alert-success' : 'alert-error', $result['message']);
return redirect(route('admin_abos_detail', [$id]));
}
public function externDone($id, AboExternalCompletionService $externalCompletion)
{
$user_abo = UserAbo::findOrFail($id);
$result = $externalCompletion->completeForAbo($user_abo);
\Session()->flash($result['success'] ? 'alert-success' : 'alert-error', $result['message']);
return redirect(route('admin_abos_detail', [$id]));
}
public function datatable()
{
$query = UserAbo::with('user_abo_orders')->with('shopping_user')->select('user_abos.*');
set_user_attr('filter_member_id', Request::get('filter_member_id'));
if (Request::get('filter_member_id') != '') {
$query->where('user_id', '=', Request::get('filter_member_id'));
}
set_user_attr('filter_status', Request::get('filter_status'));
if (Request::get('filter_status') != '') {
$query->where('status', '=', Request::get('filter_status'));
}
return \DataTables::eloquent($query)
->addColumn('id', function (UserAbo $user_abo) {
return '<a href="'.route('admin_abos_detail', [$user_abo->id]).'" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>';
})
->addColumn('start_date', function (UserAbo $user_abo) {
return $user_abo->start_date;
})
->addColumn('next_date', function (UserAbo $user_abo) {
return $user_abo->next_date;
})
->addColumn('abo_interval', function (UserAbo $user_abo) {
return \App\Services\HTMLHelper::getAboStrLang($user_abo->abo_interval);
})
->addColumn('status', function (UserAbo $user_abo) {
return $user_abo->getStatusFormated();
})
->addColumn('active', function (UserAbo $user_abo) {
return get_active_badge($user_abo->active);
})
->addColumn('is_for', function (UserAbo $user_abo) {
return $user_abo->getIsForFormated();
})
->addColumn('count', function (UserAbo $user_abo) {
return $user_abo->getCountOrders();
})
->addColumn('amount', function (UserAbo $user_abo) {
return $user_abo->getFormattedAmount().' €';
})
->addColumn('payment', function (UserAbo $user_abo) {
return $user_abo->getPaymentType();
})
->addColumn('member', function (UserAbo $user_abo) {
if (isset($user_abo->shopping_user) && $user_abo->shopping_user->member_id > 0) {
return '<a href="'.route('admin_lead_edit', [$user_abo->shopping_user->member_id]).'">'.$user_abo->shopping_user->member->getFullName().'</a>';
}
})
->addColumn('payone_userid', function (UserAbo $user_abo) {
return $user_abo->payone_userid;
})
->orderColumn('id', 'id $1')
->orderColumn('start_date', 'start_date $1')
->orderColumn('next_date', 'next_date $1')
->orderColumn('abo_interval', 'abo_interval $1')
->orderColumn('status', 'status $1')
->orderColumn('active', 'active $1')
->orderColumn('is_for', 'is_for $1')
->orderColumn('count', 'count $1')
->orderColumn('amount', 'amount $1')
->orderColumn('payone_userid', 'payone_userid $1')
->rawColumns(['id', 'status', 'active', 'is_for', 'member'])
->make(true);
}
}