Admin-Abo: Produkte trotz Mindestlaufzeit abziehen/entfernen
Die Admin-Detailseite postete Warenkorb-Aktionen mit view=me/ot, wodurch Add-only-Modus und Change-Lock auch fuer Admins griffen (Reduzieren wurde verworfen, Entfernen lieferte 403). Formular postet jetzt mit view=admin; checkPermissions laesst view=admin nur noch fuer Admins zu (schliesst zugleich die Luecke, dass unbekannte Views alle Besitzer-Pruefungen uebersprangen). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ce07bee589
commit
9dc138e45b
3 changed files with 208 additions and 3 deletions
|
|
@ -145,8 +145,10 @@ class AboController extends Controller
|
|||
$this->checkPermissions($view, $user_abo);
|
||||
$editView = \Auth::user()?->isAdmin() ? 'admin' : $view;
|
||||
// Add-only (Mindestlaufzeit) und Change-Lock richten sich nach der Abo-Sicht ($view),
|
||||
// NICHT nach Admin-Status – auf der Selbstbedienungs-Seite gelten für alle (auch VIP/Admin)
|
||||
// die Berater-Regeln. $editView dient nur dem History-Logging.
|
||||
// NICHT nach Admin-Status – auf der Selbstbedienungs-Seite (view me/ot) gelten für alle
|
||||
// (auch VIP/Admin) die Berater-Regeln. Die Admin-Detailseite postet mit view=admin
|
||||
// (nur für Admins zugelassen, siehe checkPermissions) und umgeht beide Sperren.
|
||||
// $editView dient nur dem History-Logging.
|
||||
$isAddOnlyMode = AboHelper::isAddOnlyMode($user_abo, $view);
|
||||
$changeLocked = AboHelper::isAboChangeLocked($user_abo, $view);
|
||||
|
||||
|
|
@ -753,6 +755,12 @@ class AboController extends Controller
|
|||
return;
|
||||
}
|
||||
|
||||
// view=admin (und alle unbekannten Views) sind Nicht-Admins nicht erlaubt,
|
||||
// sonst würden sämtliche is_for-/Besitzer-Prüfungen unten übersprungen.
|
||||
if (! in_array($view, ['me', 'ot'], true)) {
|
||||
abort(403, 'Unauthorized action. Invalid view');
|
||||
}
|
||||
|
||||
if ($view === 'me' && $user_abo->is_for !== 'me') {
|
||||
abort(403, 'Unauthorized action. Is not for me');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
|
||||
{!! Form::open([
|
||||
'action' => route('user_abos_update', [$view, $user_abo->id]),
|
||||
'action' => route('user_abos_update', ['admin', $user_abo->id]),
|
||||
'class' => 'form-horizontal',
|
||||
'id' => 'cart-order-form',
|
||||
'data-add-only-mode' => '0',
|
||||
|
|
|
|||
197
tests/Feature/AdminAboEditDuringMinDurationTest.php
Normal file
197
tests/Feature/AdminAboEditDuringMinDurationTest.php
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\User\AboController;
|
||||
use App\Models\Country;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboItem;
|
||||
use App\Models\UserAccount;
|
||||
use App\Repositories\AboRepository;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
uses(Tests\TestCase::class, RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* Berater-Abo ('me') innerhalb der Mindestlaufzeit (0 bezahlte Lieferungen, Minimum 6)
|
||||
* mit zwei Basis-Produkten, außerhalb des Change-Lock-Fensters (next_date weit entfernt).
|
||||
*
|
||||
* @return array{abo: UserAbo, consultant: User, itemA: UserAboItem, itemB: UserAboItem}
|
||||
*/
|
||||
function makeMinDurationAboFixture(): array
|
||||
{
|
||||
Setting::setContentBySlug('abo-min-duration', 6, 'int');
|
||||
|
||||
$country = Country::create([
|
||||
'code' => 'DE', 'phone' => '49', 'en' => 'Germany', 'de' => 'Deutschland',
|
||||
'es' => 'Alemania', 'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Германия',
|
||||
]);
|
||||
$shipping = Shipping::create(['name' => 'Standard', 'active' => true]);
|
||||
ShippingCountry::create(['shipping_id' => $shipping->id, 'country_id' => $country->id]);
|
||||
|
||||
$account = UserAccount::create([
|
||||
'salutation' => 'Herr',
|
||||
'first_name' => 'Max',
|
||||
'last_name' => 'Muster',
|
||||
'address' => 'Musterstr. 1',
|
||||
'zipcode' => '12345',
|
||||
'city' => 'Musterstadt',
|
||||
'country_id' => $country->id,
|
||||
'phone' => '123456',
|
||||
'same_as_billing' => 1,
|
||||
'language' => 'de',
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'admin' => 0,
|
||||
'account_id' => $account->id,
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => $consultant->email,
|
||||
'shipping_email' => $consultant->email,
|
||||
'billing_firstname' => 'Max',
|
||||
'billing_lastname' => 'Muster',
|
||||
'shipping_firstname' => 'Max',
|
||||
'shipping_lastname' => 'Muster',
|
||||
'same_as_billing' => 1,
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
]);
|
||||
|
||||
$abo = UserAbo::create([
|
||||
'user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $consultant->email,
|
||||
'payone_userid' => random_int(100000, 999999),
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 5,
|
||||
'next_date' => now()->addDays(30)->toDateString(),
|
||||
]);
|
||||
|
||||
$productA = makeProduct(['12'], 50, 19);
|
||||
$productB = makeProduct(['12'], 30, 19);
|
||||
|
||||
$itemA = UserAboItem::create([
|
||||
'user_abo_id' => $abo->id, 'product_id' => $productA->id, 'comp' => 0, 'qty' => 3, 'status' => 1,
|
||||
]);
|
||||
$itemB = UserAboItem::create([
|
||||
'user_abo_id' => $abo->id, 'product_id' => $productB->id, 'comp' => 0, 'qty' => 1, 'status' => 1,
|
||||
]);
|
||||
|
||||
return ['abo' => $abo, 'consultant' => $consultant, 'itemA' => $itemA, 'itemB' => $itemB];
|
||||
}
|
||||
|
||||
function makeAboAdminUser(): User
|
||||
{
|
||||
return User::forceCreate([
|
||||
'email' => 'admin-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'admin' => 2,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simuliert den AJAX-Call der Abo-Detailseite (iq-modal-cart.js) an AboController@update.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
function callAboCartUpdate(string $view, int $aboId, array $payload): mixed
|
||||
{
|
||||
$request = app('request');
|
||||
$request->headers->set('X-Requested-With', 'XMLHttpRequest');
|
||||
$request->merge($payload);
|
||||
|
||||
return (new AboController(app(AboRepository::class)))->update($view, $aboId);
|
||||
}
|
||||
|
||||
describe('Admin bearbeitet Abo-Produkte trotz Mindestlaufzeit', function () {
|
||||
it('erlaubt dem Admin das Reduzieren der Menge während der Mindestlaufzeit', function () {
|
||||
$fixture = makeMinDurationAboFixture();
|
||||
$this->actingAs(makeAboAdminUser());
|
||||
|
||||
$response = callAboCartUpdate('admin', $fixture['abo']->id, [
|
||||
'action' => 'updateCart',
|
||||
'product_id' => $fixture['itemA']->product_id,
|
||||
'order_item_id' => $fixture['itemA']->id,
|
||||
'cart_order_id' => $fixture['abo']->id,
|
||||
'qty' => 2,
|
||||
]);
|
||||
|
||||
expect($response->getStatusCode())->toBe(200)
|
||||
->and($fixture['itemA']->fresh()->qty)->toBe(2);
|
||||
});
|
||||
|
||||
it('erlaubt dem Admin das Entfernen eines Produkts während der Mindestlaufzeit', function () {
|
||||
$fixture = makeMinDurationAboFixture();
|
||||
$this->actingAs(makeAboAdminUser());
|
||||
|
||||
$response = callAboCartUpdate('admin', $fixture['abo']->id, [
|
||||
'action' => 'removeFromCart',
|
||||
'product_id' => $fixture['itemB']->product_id,
|
||||
'order_item_id' => $fixture['itemB']->id,
|
||||
'cart_order_id' => $fixture['abo']->id,
|
||||
]);
|
||||
|
||||
expect($response->getStatusCode())->toBe(200)
|
||||
->and(UserAboItem::find($fixture['itemB']->id))->toBeNull();
|
||||
});
|
||||
|
||||
it('blockiert das Reduzieren für den Berater weiterhin (Add-only, Menge bleibt)', function () {
|
||||
$fixture = makeMinDurationAboFixture();
|
||||
$this->actingAs($fixture['consultant']);
|
||||
|
||||
callAboCartUpdate('me', $fixture['abo']->id, [
|
||||
'action' => 'updateCart',
|
||||
'product_id' => $fixture['itemA']->product_id,
|
||||
'order_item_id' => $fixture['itemA']->id,
|
||||
'cart_order_id' => $fixture['abo']->id,
|
||||
'qty' => 1,
|
||||
]);
|
||||
|
||||
expect($fixture['itemA']->fresh()->qty)->toBe(3);
|
||||
});
|
||||
|
||||
it('blockiert das Entfernen für den Berater weiterhin (Add-only, 403)', function () {
|
||||
$fixture = makeMinDurationAboFixture();
|
||||
$this->actingAs($fixture['consultant']);
|
||||
|
||||
$response = callAboCartUpdate('me', $fixture['abo']->id, [
|
||||
'action' => 'removeFromCart',
|
||||
'product_id' => $fixture['itemB']->product_id,
|
||||
'order_item_id' => $fixture['itemB']->id,
|
||||
'cart_order_id' => $fixture['abo']->id,
|
||||
]);
|
||||
|
||||
expect($response->getStatusCode())->toBe(403)
|
||||
->and(UserAboItem::find($fixture['itemB']->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
it('verbietet Nicht-Admins die Admin-Sicht (view=admin => 403)', function () {
|
||||
$fixture = makeMinDurationAboFixture();
|
||||
$this->actingAs($fixture['consultant']);
|
||||
|
||||
expect(fn () => callAboCartUpdate('admin', $fixture['abo']->id, [
|
||||
'action' => 'removeFromCart',
|
||||
'product_id' => $fixture['itemB']->product_id,
|
||||
'order_item_id' => $fixture['itemB']->id,
|
||||
'cart_order_id' => $fixture['abo']->id,
|
||||
]))->toThrow(HttpException::class);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue