Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert
Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
147 lines
4.9 KiB
PHP
147 lines
4.9 KiB
PHP
<?php
|
|
|
|
use App\Models\Country;
|
|
use App\Models\Shipping;
|
|
use App\Models\ShippingCountry;
|
|
use App\Models\UserAccount;
|
|
use App\Services\UserService;
|
|
use App\Services\VatIdService;
|
|
use App\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
use function Pest\Laravel\mock;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
function makeVatCheckoutUser(array $accountOverrides = []): 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,
|
|
]);
|
|
|
|
$account = UserAccount::create(array_merge([
|
|
'first_name' => 'Marlene',
|
|
'last_name' => 'Mathis',
|
|
'country_id' => $country->id,
|
|
'tax_identification_number' => 'ATU64282555',
|
|
'taxable_sales' => 1,
|
|
], $accountOverrides));
|
|
|
|
// reverse_charge* sind nicht fillable -> explizit setzen
|
|
$account->forceFill(array_merge([
|
|
'reverse_charge' => 1,
|
|
'reverse_charge_valid' => now(),
|
|
'reverse_charge_code' => 'AT',
|
|
], array_intersect_key($accountOverrides, array_flip(['reverse_charge', 'reverse_charge_valid', 'reverse_charge_code']))))->save();
|
|
|
|
$user = User::forceCreate([
|
|
'account_id' => $account->id,
|
|
'email' => 'vat-'.uniqid('', true).'@example.com',
|
|
'password' => bcrypt('secret'),
|
|
'lang' => 'de',
|
|
]);
|
|
|
|
return [$user->fresh(), $shippingCountry];
|
|
}
|
|
|
|
beforeEach(function () {
|
|
UserService::$user_reverse_charge = false;
|
|
});
|
|
|
|
it('keeps tax free status when vies is unavailable', function () {
|
|
// VIES_ENABLED=false in phpunit.xml -> CHECK_UNAVAILABLE -> gespeicherter Status gilt
|
|
[$user, $shippingCountry] = makeVatCheckoutUser();
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeTrue();
|
|
expect(UserService::$user_reverse_charge)->toBeTrue();
|
|
expect($user->account->fresh()->reverse_charge)->toBeTruthy();
|
|
});
|
|
|
|
it('revokes reverse charge for german vat ids', function () {
|
|
[$user, $shippingCountry] = makeVatCheckoutUser([
|
|
'tax_identification_number' => 'DE123456789',
|
|
]);
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeFalse();
|
|
expect(UserService::$user_reverse_charge)->toBeFalse();
|
|
|
|
$account = $user->account->fresh();
|
|
expect($account->reverse_charge)->toBeFalsy();
|
|
expect($account->reverse_charge_valid)->toBeNull();
|
|
});
|
|
|
|
it('revokes reverse charge for implausible vat ids', function () {
|
|
[$user, $shippingCountry] = makeVatCheckoutUser([
|
|
'tax_identification_number' => '53618009',
|
|
]);
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeFalse();
|
|
expect($user->account->fresh()->reverse_charge)->toBeFalsy();
|
|
});
|
|
|
|
it('revokes reverse charge when vies reports the id as invalid', function () {
|
|
[$user, $shippingCountry] = makeVatCheckoutUser();
|
|
|
|
mock(VatIdService::class, function ($mock) {
|
|
$mock->shouldReceive('qualifiesForReverseCharge')->andReturn(true);
|
|
$mock->shouldReceive('checkViesCached')->andReturn(VatIdService::CHECK_INVALID);
|
|
});
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeFalse();
|
|
|
|
$account = $user->account->fresh();
|
|
expect($account->reverse_charge)->toBeFalsy();
|
|
expect($account->reverse_charge_valid)->toBeNull();
|
|
// Die USt-ID selbst bleibt erhalten, damit der User sie neu validieren kann
|
|
expect($account->tax_identification_number)->toBe('ATU64282555');
|
|
});
|
|
|
|
it('grants tax free checkout when vies confirms the id', function () {
|
|
[$user, $shippingCountry] = makeVatCheckoutUser();
|
|
|
|
mock(VatIdService::class, function ($mock) {
|
|
$mock->shouldReceive('qualifiesForReverseCharge')->andReturn(true);
|
|
$mock->shouldReceive('checkViesCached')->andReturn(VatIdService::CHECK_VALID);
|
|
});
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeTrue();
|
|
expect(UserService::$user_reverse_charge)->toBeTrue();
|
|
expect($user->account->fresh()->reverse_charge)->toBeTruthy();
|
|
});
|
|
|
|
it('does not run vies check when shipping country does not match reverse charge code', function () {
|
|
[$user, $shippingCountry] = makeVatCheckoutUser([
|
|
'reverse_charge_code' => 'ES',
|
|
]);
|
|
|
|
$result = UserService::performUserTaxShippingCountry($user, $shippingCountry);
|
|
|
|
expect($result)->toBeFalse();
|
|
// Kein Entzug: Der Status bleibt für passende Lieferländer bestehen
|
|
expect($user->account->fresh()->reverse_charge)->toBeTruthy();
|
|
});
|