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>
295 lines
9.3 KiB
PHP
295 lines
9.3 KiB
PHP
<?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);
|
|
});
|
|
});
|