Abo-Wartungs-Commands & Ausfuehrungs-Reminder aktivieren
Neue Artisan-Commands CreateAboReplacementOrder (Ersatzbestellung fuer ein Abo anlegen) und RepairAboOrderUserAssignment (fehlerhafte User-Zuordnung von Abo-Bestellungen korrigieren), jeweils mit Feature-Tests. Ausserdem den abo:send-execution-reminder-Schedule (06:00) aktiviert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c175a1c077
commit
26b95ee88e
5 changed files with 1156 additions and 1 deletions
295
tests/Feature/CreateAboReplacementOrderTest.php
Normal file
295
tests/Feature/CreateAboReplacementOrderTest.php
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\PaymentTransaction;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShippingPrice;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserAccount;
|
||||
use App\Models\UserInvoice;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function createAboReplacementOrderSeed(): array
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'AT',
|
||||
'phone' => '43',
|
||||
'en' => 'Austria',
|
||||
'de' => 'Österreich',
|
||||
'es' => 'Austria',
|
||||
'fr' => 'Autriche',
|
||||
'it' => 'Austria',
|
||||
'ru' => 'Австрия',
|
||||
'eu_country' => true,
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Österreich',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
ShippingPrice::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'price' => 12.90,
|
||||
'price_comp' => 37.00,
|
||||
'tax_rate' => 19.00,
|
||||
'weight_from' => 1,
|
||||
'weight_to' => 20000,
|
||||
]);
|
||||
|
||||
$account = UserAccount::create([
|
||||
'first_name' => 'Marlene',
|
||||
'last_name' => 'Mathis',
|
||||
'address' => 'Oberdorferstrasse 11b',
|
||||
'zipcode' => '6850',
|
||||
'city' => 'Dornbirn',
|
||||
'country_id' => $country->id,
|
||||
'same_as_billing' => false,
|
||||
'shipping_firstname' => 'Marlene',
|
||||
'shipping_lastname' => 'Mathis',
|
||||
'shipping_address' => 'Oberdorferstrasse 11b',
|
||||
'shipping_zipcode' => '6850',
|
||||
'shipping_city' => 'Dornbirn',
|
||||
'shipping_country_id' => $country->id,
|
||||
'reverse_charge' => true,
|
||||
'reverse_charge_valid' => now(),
|
||||
'reverse_charge_code' => 'AT',
|
||||
'tax_identification_number' => 'ATU64282555',
|
||||
'taxable_sales' => 1,
|
||||
]);
|
||||
|
||||
$user = User::forceCreate([
|
||||
'account_id' => $account->id,
|
||||
'email' => 'marlene@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $user->id,
|
||||
'billing_firstname' => 'Marlene',
|
||||
'billing_lastname' => 'Mathis',
|
||||
'billing_address' => 'Oberdorferstrasse 11b',
|
||||
'billing_zipcode' => '6850',
|
||||
'billing_city' => 'Dornbirn',
|
||||
'billing_country_id' => $country->id,
|
||||
'billing_email' => $user->email,
|
||||
'same_as_billing' => false,
|
||||
'shipping_firstname' => 'Marlene',
|
||||
'shipping_lastname' => 'Mathis',
|
||||
'shipping_address' => 'Oberdorferstrasse 11b',
|
||||
'shipping_zipcode' => '6850',
|
||||
'shipping_city' => 'Dornbirn',
|
||||
'shipping_country_id' => $country->id,
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
'mode' => 'live',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => $user->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $user->email,
|
||||
'payone_userid' => 123456,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 5,
|
||||
'next_date' => now()->addDays(3),
|
||||
]);
|
||||
|
||||
$productA = makeProduct(['2'], 119, 19);
|
||||
$productB = makeProduct(['2'], 10.70, 7);
|
||||
|
||||
$sourceOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $user->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'language' => 'de',
|
||||
'user_shop_id' => 1,
|
||||
'payment_for' => 3,
|
||||
'total' => 248.70,
|
||||
'subtotal' => 210.00,
|
||||
'shipping' => 39.90,
|
||||
'shipping_net' => 33.53,
|
||||
'subtotal_ws' => 243.53,
|
||||
'tax' => 45.07,
|
||||
'tax_split' => ['19' => '44.37', '7' => '0.70'],
|
||||
'net_split' => ['19' => '233.53', '7' => '10.00'],
|
||||
'total_shipping' => 288.60,
|
||||
'points' => 20,
|
||||
'weight' => 18100,
|
||||
'paid' => true,
|
||||
'shipped' => 10,
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 5,
|
||||
'txaction' => 'cancelled',
|
||||
'mode' => 'live',
|
||||
]);
|
||||
|
||||
ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'row_id' => 'row-a',
|
||||
'product_id' => $productA->id,
|
||||
'qty' => 2,
|
||||
'price' => 119.00,
|
||||
'price_net' => 100.00,
|
||||
'tax_rate' => 19.00,
|
||||
'tax' => 19.00,
|
||||
'price_vk_net' => 100.00,
|
||||
'discount' => 30.00,
|
||||
'points' => 10,
|
||||
'slug' => $productA->slug,
|
||||
]);
|
||||
|
||||
ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'row_id' => 'row-b',
|
||||
'product_id' => $productB->id,
|
||||
'qty' => 1,
|
||||
'price' => 10.70,
|
||||
'price_net' => 10.00,
|
||||
'tax_rate' => 7.00,
|
||||
'tax' => 0.70,
|
||||
'price_vk_net' => 10.00,
|
||||
'discount' => 30.00,
|
||||
'points' => 0,
|
||||
'slug' => $productB->slug,
|
||||
]);
|
||||
|
||||
$sourcePayment = ShoppingPayment::create([
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'clearingtype' => 'cc',
|
||||
'reference' => 'source-payment',
|
||||
'amount' => 28860,
|
||||
'currency' => 'EUR',
|
||||
'mode' => 'live',
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 5,
|
||||
]);
|
||||
$sourcePayment->txaction = 'paid';
|
||||
$sourcePayment->status = 'APPROVED';
|
||||
$sourcePayment->save();
|
||||
|
||||
PaymentTransaction::create([
|
||||
'shopping_payment_id' => $sourcePayment->id,
|
||||
'request' => 'authorization',
|
||||
'txid' => 111,
|
||||
'userid' => 222,
|
||||
'status' => 'APPROVED',
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'live',
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
$originalInvoice = UserInvoice::create([
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'year' => now()->year,
|
||||
'month' => now()->month,
|
||||
'date' => now(),
|
||||
'full_number' => '202600001',
|
||||
'number' => 1,
|
||||
'filename' => 'original.pdf',
|
||||
'dir' => 'invoice/2026/06/',
|
||||
'delivery_filename' => 'delivery.pdf',
|
||||
'delivery_dir' => 'delivery/2026/06/',
|
||||
'disk' => 'public',
|
||||
'cancellation' => true,
|
||||
'cancellation_id' => 2,
|
||||
'cancellation_date' => now(),
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$cancellationInvoice = UserInvoice::create([
|
||||
'shopping_order_id' => $sourceOrder->id,
|
||||
'year' => now()->year,
|
||||
'month' => now()->month,
|
||||
'date' => now(),
|
||||
'full_number' => '202600002',
|
||||
'number' => 2,
|
||||
'filename' => 'cancellation.pdf',
|
||||
'dir' => 'invoice/2026/06/',
|
||||
'delivery_filename' => 'cancellation-delivery.pdf',
|
||||
'delivery_dir' => 'delivery/2026/06/',
|
||||
'disk' => 'public',
|
||||
'cancellation' => true,
|
||||
'status' => 11,
|
||||
]);
|
||||
|
||||
$originalInvoice->cancellation_id = $cancellationInvoice->id;
|
||||
$originalInvoice->save();
|
||||
|
||||
return [$sourceOrder, $userAbo, $user];
|
||||
}
|
||||
|
||||
it('zeigt die Ersatzbestellung im Trockenlauf ohne Aenderung', function () {
|
||||
[$sourceOrder, $userAbo, $user] = createAboReplacementOrderSeed();
|
||||
|
||||
$exit = Artisan::call('abo:create-replacement-order', [
|
||||
'--source-order' => (string) $sourceOrder->id,
|
||||
'--abo' => (string) $userAbo->id,
|
||||
'--user' => (string) $user->id,
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
expect(ShoppingOrder::where('customer_order_source_comment', 'like', '%replacement_for_order:%')->exists())->toBeFalse();
|
||||
});
|
||||
|
||||
it('erstellt eine steuerfreie Ersatzbestellung ohne neue Zahlung auszuloesen', function () {
|
||||
[$sourceOrder, $userAbo, $user] = createAboReplacementOrderSeed();
|
||||
|
||||
$exit = Artisan::call('abo:create-replacement-order', [
|
||||
'--fix' => true,
|
||||
'--force' => true,
|
||||
'--no-invoice' => true,
|
||||
'--source-order' => (string) $sourceOrder->id,
|
||||
'--abo' => (string) $userAbo->id,
|
||||
'--user' => (string) $user->id,
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
|
||||
$replacement = ShoppingOrder::where('customer_order_source_comment', 'like', '%replacement_for_order:'.$sourceOrder->id.'%')->first();
|
||||
|
||||
expect($replacement)->not->toBeNull()
|
||||
->and((float) $replacement->subtotal)->toBe(210.00)
|
||||
->and((float) $replacement->shipping)->toBe(37.00)
|
||||
->and((float) $replacement->shipping_net)->toBe(31.09)
|
||||
->and((float) $replacement->tax)->toBe(0.00)
|
||||
->and((float) $replacement->total_shipping)->toBe(241.09)
|
||||
->and($replacement->tax_split)->toBeNull()
|
||||
->and($replacement->net_split)->toBeNull()
|
||||
->and($replacement->txaction)->toBe('extern_paid')
|
||||
->and($replacement->shopping_order_items()->count())->toBe(2)
|
||||
->and(UserAboOrder::where('shopping_order_id', $replacement->id)->where('user_abo_id', $userAbo->id)->exists())->toBeTrue()
|
||||
->and(ShoppingPayment::where('shopping_order_id', $replacement->id)->value('amount'))->toBe(24109);
|
||||
|
||||
$replacement->shopping_order_items->each(function (ShoppingOrderItem $item) {
|
||||
expect((float) $item->tax_rate)->toBe(0.00)
|
||||
->and((float) $item->tax)->toBe(0.00)
|
||||
->and((float) $item->price)->toBe((float) $item->price_net);
|
||||
});
|
||||
});
|
||||
179
tests/Feature/RepairAboOrderUserAssignmentTest.php
Normal file
179
tests/Feature/RepairAboOrderUserAssignmentTest.php
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserAccount;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function repairAboOrderUserAssignmentMakeUser(string $email, array $accountData): User
|
||||
{
|
||||
$country = Country::firstOrFail();
|
||||
$account = UserAccount::create(array_merge([
|
||||
'salutation' => 'mr',
|
||||
'first_name' => 'Vorname',
|
||||
'last_name' => 'Nachname',
|
||||
'address' => 'Alte Strasse 1',
|
||||
'zipcode' => '10000',
|
||||
'city' => 'Berlin',
|
||||
'country_id' => $country->id,
|
||||
'same_as_billing' => true,
|
||||
], $accountData));
|
||||
|
||||
return User::forceCreate([
|
||||
'account_id' => $account->id,
|
||||
'email' => $email,
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
}
|
||||
|
||||
function repairAboOrderUserAssignmentSeed(): array
|
||||
{
|
||||
makeShopEnv();
|
||||
|
||||
$oldUser = repairAboOrderUserAssignmentMakeUser('old@example.com', [
|
||||
'first_name' => 'Alter',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Falsche Strasse 1',
|
||||
'zipcode' => '11111',
|
||||
'city' => 'Altstadt',
|
||||
]);
|
||||
|
||||
$targetUser = repairAboOrderUserAssignmentMakeUser('target@example.com', [
|
||||
'first_name' => 'Richtiger',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Richtige Strasse 455',
|
||||
'zipcode' => '45500',
|
||||
'city' => 'Zielstadt',
|
||||
]);
|
||||
|
||||
$wrongShoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $oldUser->id,
|
||||
'billing_firstname' => 'Alter',
|
||||
'billing_lastname' => 'User',
|
||||
'billing_address' => 'Falsche Strasse 1',
|
||||
'billing_zipcode' => '11111',
|
||||
'billing_city' => 'Altstadt',
|
||||
'billing_country_id' => Country::firstOrFail()->id,
|
||||
'billing_email' => $oldUser->email,
|
||||
'same_as_billing' => true,
|
||||
'shipping_country_id' => Country::firstOrFail()->id,
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
'mode' => 'live',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => $oldUser->id,
|
||||
'shopping_user_id' => $wrongShoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $oldUser->email,
|
||||
'payone_userid' => 900100,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 5,
|
||||
'next_date' => now()->addDays(3),
|
||||
]);
|
||||
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $wrongShoppingUser->id,
|
||||
'auth_user_id' => $oldUser->id,
|
||||
'country_id' => 1,
|
||||
'language' => 'de',
|
||||
'user_shop_id' => 1,
|
||||
'payment_for' => 3,
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
'shipping' => 10,
|
||||
'shipping_net' => 8.4,
|
||||
'subtotal_ws' => 98.4,
|
||||
'tax' => 11.6,
|
||||
'total_shipping' => 100,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 5,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'live',
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $order->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
UserSalesVolume::create([
|
||||
'user_id' => $oldUser->id,
|
||||
'shopping_order_id' => $order->id,
|
||||
'month' => now()->month,
|
||||
'year' => now()->year,
|
||||
'date' => now(),
|
||||
'points' => 10,
|
||||
'total_net' => 90,
|
||||
'status_points' => 1,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
return [$oldUser, $targetUser, $userAbo, $order, $wrongShoppingUser];
|
||||
}
|
||||
|
||||
it('zeigt die betroffene Abo-Bestellung im Trockenlauf ohne Aenderung', function () {
|
||||
[$oldUser, $targetUser, $userAbo, $order, $wrongShoppingUser] = repairAboOrderUserAssignmentSeed();
|
||||
|
||||
$exit = Artisan::call('abo:repair-order-user', [
|
||||
'--abo' => (string) $userAbo->id,
|
||||
'--user' => (string) $targetUser->id,
|
||||
'--order' => (string) $order->id,
|
||||
'--old-user' => (string) $oldUser->id,
|
||||
'--no-documents' => true,
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
expect($order->fresh()->auth_user_id)->toBe($oldUser->id);
|
||||
expect($order->fresh()->shopping_user_id)->toBe($wrongShoppingUser->id);
|
||||
});
|
||||
|
||||
it('korrigiert die letzte Abo-Bestellung auf den Ziel-User', function () {
|
||||
[$oldUser, $targetUser, $userAbo, $order, $wrongShoppingUser] = repairAboOrderUserAssignmentSeed();
|
||||
|
||||
$exit = Artisan::call('abo:repair-order-user', [
|
||||
'--fix' => true,
|
||||
'--force' => true,
|
||||
'--abo' => (string) $userAbo->id,
|
||||
'--user' => (string) $targetUser->id,
|
||||
'--order' => (string) $order->id,
|
||||
'--old-user' => (string) $oldUser->id,
|
||||
'--no-documents' => true,
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
|
||||
$order->refresh();
|
||||
$userAbo->refresh();
|
||||
|
||||
expect($userAbo->user_id)->toBe($targetUser->id)
|
||||
->and($userAbo->email)->toBe($targetUser->email)
|
||||
->and($order->auth_user_id)->toBe($targetUser->id)
|
||||
->and($order->shopping_user_id)->not->toBe($wrongShoppingUser->id)
|
||||
->and(UserSalesVolume::where('shopping_order_id', $order->id)->first()->user_id)->toBe($targetUser->id);
|
||||
|
||||
$newShoppingUser = $order->shopping_user;
|
||||
|
||||
expect($newShoppingUser->auth_user_id)->toBe($targetUser->id)
|
||||
->and($newShoppingUser->billing_firstname)->toBe('Richtiger')
|
||||
->and($newShoppingUser->billing_lastname)->toBe('User')
|
||||
->and($newShoppingUser->billing_address)->toBe('Richtige Strasse 455')
|
||||
->and($newShoppingUser->billing_email)->toBe($targetUser->email);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue