Warenwirtschaft: SalesController::store() gehärtet (Validierung, Transaktion, Lock)
Vorbereitung AP-13: Versand-Statuswechsel validiert Eingaben (Status aus shippedTypes, Action-Whitelist), läuft in DB::transaction mit lockForUpdate gegen Doppelklick/parallele Tabs. Verhalten sonst unverändert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
46da71ff18
commit
6b8fcadcbf
2 changed files with 160 additions and 13 deletions
|
|
@ -10,6 +10,8 @@ use App\Services\CustomerPriority;
|
|||
use App\Services\Invoice;
|
||||
use App\Services\Payment;
|
||||
use App\Services\PaymentService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Request;
|
||||
|
||||
class SalesController extends Controller
|
||||
|
|
@ -417,10 +419,19 @@ class SalesController extends Controller
|
|||
if (! isset($data['id'])) {
|
||||
abort(404);
|
||||
}
|
||||
if (isset($data['action'])) {
|
||||
if ($data['action'] === 'store_shipped' && isset($data['shipped'])) {
|
||||
$shopping_order = ShoppingOrder::findOrFail($data['id']);
|
||||
$shopping_order->shipped = $data['shipped'];
|
||||
Request::validate([
|
||||
'id' => ['required', 'integer', 'exists:shopping_orders,id'],
|
||||
'action' => ['required', Rule::in(['store_shipped', 'store_txaction'])],
|
||||
'shipped' => ['required_if:action,store_shipped', 'integer', Rule::in(array_keys(ShoppingOrder::$shippedTypes))],
|
||||
'txaction' => ['required_if:action,store_txaction', 'string', 'max:20'],
|
||||
'payment_id' => ['required_if:action,store_txaction', 'integer'],
|
||||
]);
|
||||
|
||||
if ($data['action'] === 'store_shipped') {
|
||||
// Statuswechsel + Margin-/Provisionsfolgen atomar; Lock gegen Doppelklick/zweiten Tab
|
||||
DB::transaction(function () use ($data) {
|
||||
$shopping_order = ShoppingOrder::query()->lockForUpdate()->findOrFail($data['id']);
|
||||
$shopping_order->shipped = (int) $data['shipped'];
|
||||
$shopping_order->save();
|
||||
// handel Promotion Product and credit by storno
|
||||
Payment::handelUserPromotionOrder($shopping_order);
|
||||
|
|
@ -474,16 +485,15 @@ class SalesController extends Controller
|
|||
$shopping_order->shopping_order_margin->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* txaction ändern
|
||||
änderung der txaction von der Bestellung, Status Zahlung, offen, bezahlt, keine zahlung */
|
||||
if ($data['action'] === 'store_txaction' && isset($data['txaction']) && isset($data['payment_id'])) {
|
||||
PaymentService::updateTransactionStatus($data['id'], $data['txaction'], $data['payment_id']);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/* txaction ändern
|
||||
änderung der txaction von der Bestellung, Status Zahlung, offen, bezahlt, keine zahlung */
|
||||
if ($data['action'] === 'store_txaction') {
|
||||
PaymentService::updateTransactionStatus($data['id'], $data['txaction'], $data['payment_id']);
|
||||
}
|
||||
|
||||
if (isset($data['back'])) {
|
||||
return redirect($data['back']);
|
||||
}
|
||||
|
|
|
|||
137
tests/Feature/SalesShippedStatusTest.php
Normal file
137
tests/Feature/SalesShippedStatusTest.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function salesMakeAdmin(int $adminLevel = 7): User
|
||||
{
|
||||
$user = User::query()->create([
|
||||
'email' => uniqid('sales_', true).'@test.example',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
$user->forceFill([
|
||||
'admin' => $adminLevel,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
function salesMakeOrder(array $overrides = []): ShoppingOrder
|
||||
{
|
||||
$country = Country::query()->forceCreate([
|
||||
'code' => 'DE',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Germanija',
|
||||
]);
|
||||
$shipping = Shipping::query()->forceCreate(['name' => 'Standard']);
|
||||
$shippingCountry = ShippingCountry::query()->forceCreate([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
$shoppingUser = ShoppingUser::query()->forceCreate([
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_firstname' => 'Test',
|
||||
'billing_lastname' => 'Kunde',
|
||||
]);
|
||||
|
||||
return ShoppingOrder::query()->forceCreate(array_merge([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => salesMakeAdmin(0)->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'shipped' => 0,
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
test('versand-status versendet setzt shipped_at', function () {
|
||||
$admin = salesMakeAdmin();
|
||||
$order = salesMakeOrder();
|
||||
|
||||
$response = $this->actingAs($admin)->post(route('admin_sales_store'), [
|
||||
'id' => $order->id,
|
||||
'action' => 'store_shipped',
|
||||
'shipped' => 2,
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$order->refresh();
|
||||
expect($order->shipped)->toBe(2)
|
||||
->and($order->shipped_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('zuruecksetzen auf offen entfernt shipped_at', function () {
|
||||
$admin = salesMakeAdmin();
|
||||
$order = salesMakeOrder(['shipped' => 2, 'shipped_at' => now()]);
|
||||
|
||||
$this->actingAs($admin)->post(route('admin_sales_store'), [
|
||||
'id' => $order->id,
|
||||
'action' => 'store_shipped',
|
||||
'shipped' => 0,
|
||||
])->assertRedirect();
|
||||
|
||||
$order->refresh();
|
||||
expect($order->shipped)->toBe(0)
|
||||
->and($order->shipped_at)->toBeNull();
|
||||
});
|
||||
|
||||
test('ungueltiger versand-status wird abgelehnt', function () {
|
||||
$admin = salesMakeAdmin();
|
||||
$order = salesMakeOrder();
|
||||
|
||||
$response = $this->actingAs($admin)->from(route('admin_sales'))->post(route('admin_sales_store'), [
|
||||
'id' => $order->id,
|
||||
'action' => 'store_shipped',
|
||||
'shipped' => 99,
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('shipped');
|
||||
expect($order->fresh()->shipped)->toBe(0);
|
||||
});
|
||||
|
||||
test('unbekannte action wird abgelehnt', function () {
|
||||
$admin = salesMakeAdmin();
|
||||
$order = salesMakeOrder();
|
||||
|
||||
$this->actingAs($admin)->post(route('admin_sales_store'), [
|
||||
'id' => $order->id,
|
||||
'action' => 'delete_everything',
|
||||
])->assertSessionHasErrors('action');
|
||||
});
|
||||
|
||||
test('nicht-admin wird umgeleitet und aendert nichts', function () {
|
||||
$user = salesMakeAdmin(1);
|
||||
$order = salesMakeOrder();
|
||||
|
||||
$this->actingAs($user)->post(route('admin_sales_store'), [
|
||||
'id' => $order->id,
|
||||
'action' => 'store_shipped',
|
||||
'shipped' => 2,
|
||||
])->assertRedirect('/home');
|
||||
|
||||
expect($order->fresh()->shipped)->toBe(0);
|
||||
});
|
||||
|
||||
test('fehlende id ergibt 404', function () {
|
||||
$admin = salesMakeAdmin();
|
||||
|
||||
$this->actingAs($admin)->post(route('admin_sales_store'), [
|
||||
'action' => 'store_shipped',
|
||||
'shipped' => 2,
|
||||
])->assertNotFound();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue