Abo Einmalprodukte: Review-Gate (VIP), Verbindlichkeit & Summen-Layout
- Live-Review-Gate: Einmalprodukte nur fuer VIP im Sales Center sichtbar, Portal ausgeblendet (AboHelper::isOneTimeFeatureVisible + Gates in Controllern) - Nur verbindlich bestaetigte Einmal-Artikel fliessen in die Lieferung; Service-Helfer confirmedItems/pendingItems/pendingGross - Footer-Layout der Einmalprodukt-Liste: bestaetigte Summe + Gesamtbetrag, Trennstrich, offener Betrag und neue Gesamtsumme (dunkelgruen) - Uebersetzungen DE/EN/ES/FR (onetime_new_total u.a.), Tests angepasst/ergaenzt Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2269ce031f
commit
8288ea59ac
16 changed files with 356 additions and 46 deletions
|
|
@ -11,6 +11,7 @@ use App\Models\UserAbo;
|
|||
use App\Models\UserCredit;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Services\AboHelper;
|
||||
use App\Services\BusinessPlan\TreeCalcBot;
|
||||
use App\Services\BusinessPlan\TreeCalcBotOptimized;
|
||||
use App\Services\DhlModalService;
|
||||
|
|
@ -172,6 +173,7 @@ class ModalController extends Controller
|
|||
}
|
||||
if ($data['action'] === 'abo-add-onetime') {
|
||||
$user_abo = UserAbo::find($data['id']);
|
||||
abort_unless($user_abo && AboHelper::isOneTimeFeatureVisible($user_abo), 403);
|
||||
$ret = view('user.abo.modal_abo_onetime_products', compact('data', 'user_abo'))->render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class AboController extends Controller
|
|||
AboOrderCart::makeOrderYard($user_abo);
|
||||
$baseCompCount = Yard::instance(AboOrderCart::INSTANCE)->getNumComp();
|
||||
|
||||
$oneTimeWindowOpen = AboHelper::isOneTimeWindowOpen($user_abo);
|
||||
$oneTimeWindowOpen = AboHelper::isOneTimeFeatureVisible($user_abo);
|
||||
if ($oneTimeWindowOpen) {
|
||||
AboOrderCart::addOneTimeItemsToYard($user_abo);
|
||||
AboOrderCart::checkNumOfCompProducts($user_abo);
|
||||
|
|
@ -211,7 +211,7 @@ class AboController extends Controller
|
|||
AboOrderCart::initYard($user_abo);
|
||||
AboOrderCart::makeOrderYard($user_abo);
|
||||
$baseCompCount = Yard::instance(AboOrderCart::INSTANCE)->getNumComp();
|
||||
if (AboHelper::isOneTimeWindowOpen($user_abo)) {
|
||||
if (AboHelper::isOneTimeFeatureVisible($user_abo)) {
|
||||
AboOrderCart::addOneTimeItemsToYard($user_abo->fresh());
|
||||
}
|
||||
AboOrderCart::checkNumOfCompProducts($user_abo);
|
||||
|
|
@ -221,7 +221,7 @@ class AboController extends Controller
|
|||
$html_cart = view('admin.abo._order_abo_show', [
|
||||
'user_abo' => $user_abo,
|
||||
'error_message' => $error_message,
|
||||
'split_mode' => AboHelper::isOneTimeWindowOpen($user_abo),
|
||||
'split_mode' => AboHelper::isOneTimeFeatureVisible($user_abo),
|
||||
'summary' => $summary,
|
||||
'add_only_mode' => $isAddOnlyMode,
|
||||
])->render();
|
||||
|
|
@ -325,6 +325,7 @@ class AboController extends Controller
|
|||
if ($data['action'] === 'abo-add-onetime') {
|
||||
$user_abo = UserAbo::find($data['id']);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
abort_unless(AboHelper::isOneTimeFeatureVisible($user_abo), 403);
|
||||
$ret = view('user.abo.modal_abo_onetime_products', compact('data', 'user_abo'))->render();
|
||||
}
|
||||
if ($data['action'] === 'abo_update_settings') {
|
||||
|
|
@ -352,7 +353,7 @@ class AboController extends Controller
|
|||
$this->checkPortalPermission($user_abo);
|
||||
$isAddOnlyMode = AboHelper::isAddOnlyMode($user_abo, $view);
|
||||
|
||||
if (! AboHelper::isOneTimeWindowOpen($user_abo)) {
|
||||
if (! AboHelper::isOneTimeFeatureVisible($user_abo)) {
|
||||
return response()->json([
|
||||
'response' => false,
|
||||
'message' => __('abo.onetime_window_closed'),
|
||||
|
|
@ -404,6 +405,8 @@ class AboController extends Controller
|
|||
$user_abo = UserAbo::findOrFail($user_abo_id);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
|
||||
abort_unless(AboHelper::isOneTimeFeatureVisible($user_abo), 403);
|
||||
|
||||
AboOrderCart::initYard($user_abo);
|
||||
|
||||
$query = Product::select('products.*')
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class AboController extends Controller
|
|||
AboOrderCart::makeOrderYard($user_abo);
|
||||
$baseCompCount = Yard::instance(AboOrderCart::INSTANCE)->getNumComp();
|
||||
|
||||
$oneTimeWindowOpen = AboHelper::isOneTimeWindowOpen($user_abo);
|
||||
$oneTimeWindowOpen = AboHelper::isOneTimeFeatureVisible($user_abo);
|
||||
if ($oneTimeWindowOpen) {
|
||||
AboOrderCart::addOneTimeItemsToYard($user_abo);
|
||||
AboOrderCart::checkNumOfCompProducts($user_abo);
|
||||
|
|
@ -233,7 +233,7 @@ class AboController extends Controller
|
|||
AboOrderCart::initYard($user_abo);
|
||||
AboOrderCart::makeOrderYard($user_abo); // reCalculateShippingPrice
|
||||
$baseCompCount = Yard::instance(AboOrderCart::INSTANCE)->getNumComp();
|
||||
if (AboHelper::isOneTimeWindowOpen($user_abo)) {
|
||||
if (AboHelper::isOneTimeFeatureVisible($user_abo)) {
|
||||
AboOrderCart::addOneTimeItemsToYard($user_abo->fresh());
|
||||
}
|
||||
AboOrderCart::checkNumOfCompProducts($user_abo); // after reCalculateShippingPrice check it and remove or add comp product
|
||||
|
|
@ -246,7 +246,7 @@ class AboController extends Controller
|
|||
$html_cart = view('admin.abo._order_abo_show', [
|
||||
'user_abo' => $user_abo,
|
||||
'error_message' => $error_message,
|
||||
'split_mode' => AboHelper::isOneTimeWindowOpen($user_abo),
|
||||
'split_mode' => AboHelper::isOneTimeFeatureVisible($user_abo),
|
||||
'summary' => $summary,
|
||||
'add_only_mode' => $isAddOnlyMode,
|
||||
])->render();
|
||||
|
|
@ -273,7 +273,7 @@ class AboController extends Controller
|
|||
$editView = \Auth::user()?->isAdmin() ? 'admin' : $view;
|
||||
$isAddOnlyMode = AboHelper::isAddOnlyMode($user_abo, $editView);
|
||||
|
||||
if (! AboHelper::isOneTimeWindowOpen($user_abo)) {
|
||||
if (! AboHelper::isOneTimeFeatureVisible($user_abo)) {
|
||||
return response()->json([
|
||||
'response' => false,
|
||||
'message' => __('abo.onetime_window_closed'),
|
||||
|
|
@ -448,6 +448,8 @@ class AboController extends Controller
|
|||
{
|
||||
$user_abo = UserAbo::findOrFail($user_abo_id);
|
||||
|
||||
abort_unless(AboHelper::isOneTimeFeatureVisible($user_abo), 403);
|
||||
|
||||
AboOrderCart::initYard($user_abo);
|
||||
|
||||
$show_on_ids = $user_abo->is_for === 'me' ? ['2'] : ['3'];
|
||||
|
|
|
|||
|
|
@ -157,10 +157,10 @@ class AboHelper
|
|||
public static function getAboTypeBadge($abo_type)
|
||||
{
|
||||
if ($abo_type === 'base') {
|
||||
return '<span class="badge badge-pill badge-warning"><i class="fas fa-star"></i> '.__('abo.'.$abo_type).'</span></a>';
|
||||
return '<span class="badge badge-pill badge-warning"><i class="fas fa-star"></i> ' . __('abo.' . $abo_type) . '</span></a>';
|
||||
}
|
||||
if ($abo_type === 'upgrade') {
|
||||
return '<span class="badge badge-pill badge-info"><i class="far fa-star"></i> '.__('abo.'.$abo_type).'</span></a>';
|
||||
return '<span class="badge badge-pill badge-info"><i class="far fa-star"></i> ' . __('abo.' . $abo_type) . '</span></a>';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
@ -209,6 +209,27 @@ class AboHelper
|
|||
return $daysUntilExecution <= self::getOneTimeWindowDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sichtbarkeit des Einmalprodukte-Features während der Live-Abstimmung.
|
||||
*
|
||||
* Temporäre Einschränkung: Das Feature wird vorerst nur VIP-Usern (Admins)
|
||||
* im Sales Center angezeigt. Das Endkunden-Portal läuft über den
|
||||
* `customers`-Guard und hat keinen VIP-User auf dem `user`-Guard, daher
|
||||
* bleibt das Feature dort vollständig ausgeblendet. Nach Abschluss der
|
||||
* Abstimmung kann diese Methode wieder auf reines isOneTimeWindowOpen()
|
||||
* zurückgesetzt werden, um das Feature für alle freizuschalten.
|
||||
*/
|
||||
public static function isOneTimeFeatureVisible(UserAbo $userAbo): bool
|
||||
{
|
||||
/* if (! self::isOneTimeWindowOpen($userAbo)) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
$user = \Auth::guard('user')->user();
|
||||
|
||||
return $user instanceof User && $user->isVIP();
|
||||
}
|
||||
|
||||
public static function getFirstAboDate($date, $abo_interval)
|
||||
{
|
||||
$reference = Carbon::parse($date)->startOfDay();
|
||||
|
|
@ -391,7 +412,7 @@ class AboHelper
|
|||
{
|
||||
$ret = [];
|
||||
foreach (self::$txaction_filter_text as $key => $val) {
|
||||
$ret[$key] = trans('payment.'.$val);
|
||||
$ret[$key] = trans('payment.' . $val);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,40 @@ class AboOneTimeService
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbindlich bestätigte Einmal-Artikel (werden mit der nächsten Lieferung versandt).
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<int, UserAboOneTimeItem>
|
||||
*/
|
||||
public static function confirmedItems(UserAbo $userAbo): \Illuminate\Support\Collection
|
||||
{
|
||||
return $userAbo->one_time_items()
|
||||
->with('product')
|
||||
->get()
|
||||
->filter(fn (UserAboOneTimeItem $item): bool => $item->isConfirmed())
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Noch nicht bestätigte Einmal-Artikel (Entwurf, nicht verbindlich).
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<int, UserAboOneTimeItem>
|
||||
*/
|
||||
public static function pendingItems(UserAbo $userAbo): \Illuminate\Support\Collection
|
||||
{
|
||||
return $userAbo->one_time_items()
|
||||
->with('product')
|
||||
->get()
|
||||
->reject(fn (UserAboOneTimeItem $item): bool => $item->isConfirmed())
|
||||
->values();
|
||||
}
|
||||
|
||||
public static function pendingGross(UserAbo $userAbo): float
|
||||
{
|
||||
return round(self::pendingItems($userAbo)
|
||||
->sum(fn (UserAboOneTimeItem $item): float => (float) $item->price * (int) $item->qty), 2);
|
||||
}
|
||||
|
||||
public static function hasUnconfirmedChanges(UserAbo $userAbo): bool
|
||||
{
|
||||
return UserAboOneTimeItem::withTrashed()
|
||||
|
|
|
|||
|
|
@ -246,7 +246,14 @@ class AboOrderCart
|
|||
|
||||
$yard = Yard::instance(self::INSTANCE);
|
||||
|
||||
foreach ($user_abo->one_time_items()->get() as $item) {
|
||||
// Nur verbindlich bestätigte Einmal-Artikel fließen in die nächste Lieferung
|
||||
// (Snapshot-Menge == bestätigte Menge). Unbestätigte Entwürfe bleiben außen vor.
|
||||
$confirmedItems = $user_abo->one_time_items()
|
||||
->whereNotNull('confirmed_at')
|
||||
->whereColumn('confirmed_qty', 'qty')
|
||||
->get();
|
||||
|
||||
foreach ($confirmedItems as $item) {
|
||||
$product = Product::find($item->product_id);
|
||||
if (! $product) {
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue