Steuerberater Modul tax
This commit is contained in:
parent
0f82fea88a
commit
245c281541
22 changed files with 1489 additions and 139 deletions
|
|
@ -1,15 +1,23 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Repositories\AboRepository;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(Tests\TestCase::class);
|
||||
uses(Tests\TestCase::class, RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repository = new AboRepository;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
/**
|
||||
* Szenario: Heute = 15. März, next_date = 5. April (März-Ausführung bereits erfolgt).
|
||||
* Änderung auf Tag 20 → neues Datum soll der 20. April sein, NICHT der 20. März.
|
||||
|
|
@ -86,6 +94,54 @@ it('verwendet heute als Referenz wenn kein next_date gesetzt ist', function () {
|
|||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('erlaubt Admins den nächsten Ausführungstermin ohne User-Sperrfrist direkt zu setzen', function () {
|
||||
Carbon::setTestNow('2026-03-19 12:00:00');
|
||||
|
||||
$abo = createRepositoryTestAbo([
|
||||
'abo_interval' => 5,
|
||||
'next_date' => '2026-03-20',
|
||||
]);
|
||||
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$result = $this->repository->update([
|
||||
'action' => 'abo_update_settings',
|
||||
'id' => $abo->id,
|
||||
'view' => 'admin',
|
||||
'abo_interval' => 20,
|
||||
'abo_next_month' => '2026-03',
|
||||
'abo_is_active' => 'true',
|
||||
]);
|
||||
|
||||
expect($result)->toBeTrue();
|
||||
expect($abo->refresh()->abo_interval)->toBe(20);
|
||||
expect($abo->getRawOriginal('next_date'))->toBe('2026-03-20');
|
||||
});
|
||||
|
||||
it('behält die User-Sperrfrist für normale Abo-Änderungen bei', function () {
|
||||
Carbon::setTestNow('2026-03-19 12:00:00');
|
||||
|
||||
$abo = createRepositoryTestAbo([
|
||||
'abo_interval' => 5,
|
||||
'next_date' => '2026-03-20',
|
||||
]);
|
||||
|
||||
$this->actingAs($abo->user);
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$result = $this->repository->update([
|
||||
'action' => 'abo_update_settings',
|
||||
'id' => $abo->id,
|
||||
'view' => 'me',
|
||||
'abo_interval' => 20,
|
||||
'abo_is_active' => 'true',
|
||||
]);
|
||||
|
||||
expect($result)->toBeFalse();
|
||||
expect($abo->refresh()->abo_interval)->toBe(5);
|
||||
expect($abo->getRawOriginal('next_date'))->toBe('2026-03-20');
|
||||
});
|
||||
|
||||
/**
|
||||
* Hilfsfunktion zum Aufruf privater Methoden.
|
||||
*
|
||||
|
|
@ -98,3 +154,53 @@ function invadePrivateMethod(object $object, string $methodName, array $args = [
|
|||
|
||||
return $reflection->invoke($object, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $overrides
|
||||
*/
|
||||
function createRepositoryTestAbo(array $overrides = []): UserAbo
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Deutschland',
|
||||
]);
|
||||
|
||||
$user = User::forceCreate([
|
||||
'email' => 'abo-repository-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'admin' => 0,
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $user->id,
|
||||
'member_id' => $user->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => $user->email,
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
]);
|
||||
|
||||
return UserAbo::create(array_merge([
|
||||
'user_id' => $user->id,
|
||||
'member_id' => $user->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $user->email,
|
||||
'payone_userid' => random_int(100000, 999999),
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 5,
|
||||
'start_date' => '2026-01-05',
|
||||
'last_date' => '2026-02-05',
|
||||
'next_date' => '2026-03-05',
|
||||
], $overrides));
|
||||
}
|
||||
|
|
|
|||
207
tests/Feature/AdminAboRetryPaymentTest.php
Normal file
207
tests/Feature/AdminAboRetryPaymentTest.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
use App\Cron\UserMakeOrder;
|
||||
use App\Models\Country;
|
||||
use App\Models\PaymentTransaction;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\AboHelper;
|
||||
use App\Services\AboRetryPaymentService;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
it('zeigt Zahlungsfehler unter fehlgeschlagenen Abo-Ausführungen an', function () {
|
||||
$fixture = createAdminAboRetryFixture();
|
||||
|
||||
$html = view('admin.abo._executions', [
|
||||
'user_abo' => $fixture['userAbo'],
|
||||
'isAdmin' => false,
|
||||
'only_show_products' => true,
|
||||
])->render();
|
||||
|
||||
expect($html)
|
||||
->toContain('Zahlung fehlgeschlagen')
|
||||
->toContain('923')
|
||||
->toContain('Keine Deckung');
|
||||
});
|
||||
|
||||
it('blockiert erneute Zahlungsversuche für nicht angehaltene Abos vor dem Payment-Aufruf', function () {
|
||||
$service = new AboRetryPaymentService;
|
||||
$userAbo = new UserAbo([
|
||||
'status' => 2,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$result = $service->retry($userAbo);
|
||||
|
||||
expect($result['success'])->toBeFalse();
|
||||
expect($result['message'])->toBe(__('abo.retry_only_hold'));
|
||||
});
|
||||
|
||||
it('speichert PAYONE-Fehler aus dem automatischen Abo-Lauf als PaymentTransaction', function () {
|
||||
$fixture = createAdminAboRetryFixture();
|
||||
$shoppingPayment = $fixture['userAboOrder']->shopping_order->shopping_payments()->firstOrFail();
|
||||
$userMakeOrder = new UserMakeOrder($fixture['userAbo']);
|
||||
|
||||
$payProperty = new ReflectionProperty($userMakeOrder, 'pay');
|
||||
$payProperty->setAccessible(true);
|
||||
$payProperty->setValue($userMakeOrder, new class($shoppingPayment)
|
||||
{
|
||||
public function __construct(private ShoppingPayment $shoppingPayment) {}
|
||||
|
||||
public function getShoppingPayment(): ShoppingPayment
|
||||
{
|
||||
return $this->shoppingPayment;
|
||||
}
|
||||
});
|
||||
|
||||
$recordMethod = new ReflectionMethod($userMakeOrder, 'recordPaymentTransaction');
|
||||
$recordMethod->setAccessible(true);
|
||||
$recordMethod->invoke($userMakeOrder, [
|
||||
'status' => 'ERROR',
|
||||
'errorcode' => 130,
|
||||
'errormessage' => 'Limit überschritten',
|
||||
'customermessage' => 'Die Zahlung wurde abgelehnt.',
|
||||
]);
|
||||
|
||||
$paymentTransaction = $shoppingPayment->payment_transactions()->latest('id')->firstOrFail();
|
||||
|
||||
expect($paymentTransaction->status)->toBe('ERROR');
|
||||
expect($paymentTransaction->errorcode)->toBe(130);
|
||||
expect($paymentTransaction->errormessage)->toBe('Limit überschritten');
|
||||
});
|
||||
|
||||
it('setzt die Mindestlaufzeit-Sperre fuer Admin-Bearbeitung ausser Kraft', function () {
|
||||
$fixture = createAdminAboRetryFixture();
|
||||
|
||||
expect(AboHelper::isAddOnlyMode($fixture['userAbo'], 'admin'))->toBeFalse();
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{userAbo: UserAbo, userAboOrder: UserAboOrder}
|
||||
*/
|
||||
function createAdminAboRetryFixture(): array
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Deutschland',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$user = User::forceCreate([
|
||||
'email' => 'admin-abo-retry-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $user->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $user->id,
|
||||
'member_id' => $user->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => $user->email,
|
||||
'shipping_email' => $user->email,
|
||||
'shipping_firstname' => 'Max',
|
||||
'shipping_lastname' => 'Muster',
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => $user->id,
|
||||
'member_id' => $user->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $user->email,
|
||||
'payone_userid' => 123456,
|
||||
'clearingtype' => 'wlt',
|
||||
'wallettype' => 'PPE',
|
||||
'active' => true,
|
||||
'status' => 3,
|
||||
'abo_interval' => 5,
|
||||
'next_date' => now()->toDateString(),
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $user->id,
|
||||
'member_id' => $user->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 3,
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
'total_shipping' => 100,
|
||||
'paid' => false,
|
||||
'is_abo' => true,
|
||||
'txaction' => 'failed',
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
$shoppingPayment = ShoppingPayment::create([
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'clearingtype' => 'wlt',
|
||||
'wallettype' => 'PPE',
|
||||
'reference' => 'RF123456',
|
||||
'amount' => 10000,
|
||||
'currency' => 'EUR',
|
||||
'mode' => 'test',
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 5,
|
||||
]);
|
||||
|
||||
PaymentTransaction::create([
|
||||
'shopping_payment_id' => $shoppingPayment->id,
|
||||
'request' => 'debit',
|
||||
'txid' => 1,
|
||||
'userid' => 123456,
|
||||
'status' => 'ERROR',
|
||||
'txaction' => 'failed',
|
||||
'errorcode' => 923,
|
||||
'errormessage' => 'Keine Deckung',
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
$userAboOrder = UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 3,
|
||||
'paid' => false,
|
||||
]);
|
||||
|
||||
return [
|
||||
'userAbo' => $userAbo,
|
||||
'userAboOrder' => $userAboOrder,
|
||||
];
|
||||
}
|
||||
|
|
@ -458,6 +458,64 @@ class DatevExportServiceTest extends TestCase
|
|||
$this->assertEquals(15.50, $result);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_parses_homeparty_ek_tax_split_format_with_preferred_key()
|
||||
{
|
||||
$method = new \ReflectionMethod(DatevExportService::class, 'parseNumber');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->service, ['vk_tax' => '15.50', 'ek_tax' => '7.50'], 'ek_tax');
|
||||
|
||||
$this->assertEquals(7.50, $result);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_parses_homeparty_ek_net_split_format_with_preferred_key()
|
||||
{
|
||||
$method = new \ReflectionMethod(DatevExportService::class, 'parseNumber');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->service, ['vk_net' => '123.45', 'ek_net' => '67.89'], 'ek_net');
|
||||
|
||||
$this->assertEquals(67.89, $result);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_resolves_net_split_from_collective_order_when_order_has_none()
|
||||
{
|
||||
$method = new \ReflectionMethod(DatevExportService::class, 'resolveNetSplit');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$collectOrder = new \stdClass;
|
||||
$collectOrder->net_split = ['19' => '533.61'];
|
||||
|
||||
$order = new \stdClass;
|
||||
$order->net_split = null;
|
||||
$order->shopping_collect_order = $collectOrder;
|
||||
|
||||
$result = $method->invoke($this->service, $order);
|
||||
|
||||
$this->assertEquals(['19' => '533.61'], $result);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_prefers_order_net_split_over_collective_order_net_split()
|
||||
{
|
||||
$method = new \ReflectionMethod(DatevExportService::class, 'resolveNetSplit');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$collectOrder = new \stdClass;
|
||||
$collectOrder->net_split = ['19' => '533.61'];
|
||||
|
||||
$order = new \stdClass;
|
||||
$order->net_split = ['19' => '677.51'];
|
||||
$order->shopping_collect_order = $collectOrder;
|
||||
|
||||
$result = $method->invoke($this->service, $order);
|
||||
|
||||
$this->assertEquals(['19' => '677.51'], $result);
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Tests
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue