gruene-seele/tests/Feature/SalesShippedStatusTest.php
Kevin Adametz 0c8d321f66 Warenwirtschaft: AP-38 — ERP-/Connector-Status-Mapping vervollständigt
Die orange Ampel bei abgeschlossenen Bestellungen ist KEIN ERP-Fehler: der
Endpoint /api/wp/status liefert für versendete Bestellungen bereits sent/close
(Live-DB: 111× shipped=2, 1× shipped=3). Ursache liegt shopseitig (Cron/Auth) —
wird per SSH geprüft. Unabhängig davon die latenten Mapping-Lücken geschlossen:

ERP (ShoppingOrder::$apiShippedTypes):
- Status 5 (Wartestellung) fehlte und lieferte den Default 'free' (im Shop gar
  kein Licht). Jetzt 5 => 'process' (Shop-Ampel orange = in Bearbeitung).

Connector (partnercenter-api, Teil von v1.6.0 — noch nicht ausgerollt):
- admin/post-list.php: Ampel-Switch deckt jetzt 'pick_up' (Abholung ⇒ grün) und
  'cancel' (storniert ⇒ neue rote Kugel) ab; vorher fielen beide auf ⚠️.
- woocommerce/order-status.php: 'pick_up' und 'cancel' gelten jetzt als
  Endzustände und fallen aus der stündlichen Nachfrage-Liste
  (_partnercenter_api_unclear_order_ids) — vorher wurden 12 stornierte
  Bestellungen dauerhaft weiter gepollt.

Test: AP-38-Mapping prüft alle shipped-Werte (kein stiller 'free'-Fallback mehr).
Suite 338 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 11:01:25 +00:00

161 lines
4.9 KiB
PHP

<?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();
});
test('AP-38: API-Status-Mapping deckt alle shipped-Werte ab (Wartestellung nicht mehr free)', function () {
// Der Shop pollt getAPIShippedType(); versendete Bestellungen müssen grün-fähig
// sein (sent/close), Wartestellung darf NICHT 'free' (im Shop kein Licht) liefern.
$expected = [
0 => 'open', // orange
1 => 'process', // orange
2 => 'sent', // grün
3 => 'close', // grün
4 => 'pick_up', // grün (Abholung)
5 => 'process', // AP-38: Wartestellung ⇒ orange statt 'free'
10 => 'cancel', // rot
];
foreach ($expected as $shipped => $apiStatus) {
$order = salesMakeOrder(['shipped' => $shipped]);
expect($order->getAPIShippedType())->toBe($apiStatus);
}
// Jeder gepflegte Versandstatus hat auch ein API-Mapping (kein stiller 'free'-Fallback).
foreach (array_keys(ShoppingOrder::$shippedTypes) as $shipped) {
expect(ShoppingOrder::$apiShippedTypes)->toHaveKey($shipped);
}
});