hasColumn('products', 'slug')) { Schema::connection('sqlite')->table('products', function ($table) { $table->string('slug')->nullable(); }); } }); afterEach(fn () => Carbon::setTestNow()); function ppaMakeUser(array $overrides = []): User { return User::forceCreate(array_merge([ 'email' => 'ppa-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'm_level' => 1, 'active_date' => '2026-08-28 16:13:11', 'payment_account' => null, 'partner_paid_at' => null, ], $overrides)); } function ppaRegistrationOrder(User $user, bool $paid = true): ShoppingOrder { return ShoppingOrder::forceCreate([ 'shopping_user_id' => 0, 'auth_user_id' => $user->id, 'country_id' => 0, 'user_shop_id' => 0, 'payment_for' => 1, 'paid' => $paid, 'created_at' => '2026-08-28 16:14:00', 'updated_at' => '2026-09-01 08:54:00', ]); } /** * Wer Ende August registriert und am 1. September bezahlt, ist ein * September-Neupartner. Massgeblich ist `partner_paid_at`, gesetzt beim ersten * Zahlungseingang und danach nie wieder. */ describe('partner_paid_at: erste Kontozahlung', function () { it('setzt den Wert nur beim ersten Mal', function () { $user = ppaMakeUser(); expect(UserUtil::markPartnerPaid($user))->toBeTrue() ->and($user->partner_paid_at->toDateTimeString())->toBe('2026-09-01 06:49:35'); Carbon::setTestNow(Carbon::parse('2027-09-01 10:00:00')); // Verlaengerung ein Jahr spaeter: der Wert bleibt stehen. expect(UserUtil::markPartnerPaid($user))->toBeFalse() ->and($user->partner_paid_at->toDateTimeString())->toBe('2026-09-01 06:49:35'); }); it('wird von der Kontozahlung gesetzt und macht den Partner zum Neupartner des Zahlungsmonats', function () { $user = ppaMakeUser(); $product = Product::forceCreate([ 'name' => 'Starterpaket', 'title' => 'Starterpaket', 'price' => 89.0, 'tax' => 19.0, 'active' => true, 'pos' => 1, 'weight' => 0, 'points' => 0, 'slug' => 'starter-'.uniqid(), 'action' => [0], // payment_for_account ]); $order = ppaRegistrationOrder($user, false); ShoppingOrderItem::forceCreate([ 'shopping_order_id' => $order->id, 'product_id' => $product->id, 'qty' => 1, 'price' => 89.0, ]); Payment::paymentStatusPaidAction($order->fresh(), true); $user->refresh(); expect($user->partner_paid_at?->toDateString())->toBe('2026-09-01') ->and($user->payment_account)->not->toBeNull() ->and($user->isNewPartnerIn(8, 2026))->toBeFalse() ->and($user->isNewPartnerIn(9, 2026))->toBeTrue(); }); }); describe('partner_paid_at: Rechnungskauf (Audit 18.09., F1)', function () { it('setzt den Wert erst bei der Zahlung der Rechnung, nicht bei der offenen Rechnung', function () { Carbon::setTestNow(Carbon::parse('2026-08-31 18:00:00')); $user = ppaMakeUser(); $product = Product::forceCreate([ 'name' => 'Starterpaket', 'title' => 'Starterpaket', 'price' => 89.0, 'tax' => 19.0, 'active' => true, 'pos' => 1, 'weight' => 0, 'points' => 0, 'slug' => 'starter-'.uniqid(), 'action' => [0], ]); $order = ppaRegistrationOrder($user, false); ShoppingOrderItem::forceCreate(['shopping_order_id' => $order->id, 'product_id' => $product->id, 'qty' => 1, 'price' => 89.0]); // Rechnungskauf: Konto wird vorab freigeschaltet, bezahlt ist noch nichts. Payment::paymentStatusPaidAction($order->fresh(), false); $user->refresh(); expect($user->payment_account)->not->toBeNull() ->and($user->partner_paid_at)->toBeNull() ->and($user->isNewPartnerIn(8, 2026))->toBeFalse(); // Zahlungseingang im September: jetzt zaehlt er, und zwar im September. Carbon::setTestNow(Carbon::parse('2026-09-01 09:00:00')); Payment::paymentStatusPaidAction($order->fresh(), true); $user->refresh(); expect($user->partner_paid_at?->toDateString())->toBe('2026-09-01') ->and($user->isNewPartnerIn(9, 2026))->toBeTrue() ->and($user->isNewPartnerIn(8, 2026))->toBeFalse(); }); }); describe('partner_paid_at: Rueckfuellung fuer bestehende Partner', function () { it('laesst eine offene Rechnung ohne bezahlte Bestellung leer statt das Aktivierungsdatum zu nehmen (Audit F1)', function () { $user = ppaMakeUser(['payment_account' => '2027-08-28 00:00:00']); ppaRegistrationOrder($user, false); expect(UserUtil::backfillPartnerPaidAt())->toBe(0) ->and($user->fresh()->partner_paid_at)->toBeNull(); }); it('laesst updated_at beim Rueckfuellen unangetastet (Audit F5)', function () { $user = ppaMakeUser(['payment_account' => '2027-08-28 00:00:00']); User::whereKey($user->id)->toBase()->update(['updated_at' => '2026-05-05 05:05:05']); UserUtil::backfillPartnerPaidAt(); expect($user->fresh()->partner_paid_at)->not->toBeNull() ->and($user->fresh()->updated_at->format('Y-m-d H:i:s'))->toBe('2026-05-05 05:05:05'); }); it('nimmt die Punktebuchung der ersten bezahlten Registrierungsbestellung', function () { $user = ppaMakeUser(['payment_account' => '2027-09-01 06:49:35']); $order = ppaRegistrationOrder($user); UserSalesVolume::forceCreate([ 'user_id' => $user->id, 'shopping_order_id' => $order->id, 'month' => 9, 'year' => 2026, 'points' => 200, 'status' => 1, 'created_at' => '2026-09-01 06:49:35', ]); expect(UserUtil::backfillPartnerPaidAt())->toBe(1) ->and($user->fresh()->partner_paid_at->toDateTimeString())->toBe('2026-09-01 06:49:35'); }); it('faellt ohne Buchung auf das Aenderungsdatum der Bestellung zurueck', function () { $user = ppaMakeUser(['payment_account' => '2027-09-01 06:49:35']); ppaRegistrationOrder($user); UserUtil::backfillPartnerPaidAt(); expect($user->fresh()->partner_paid_at->toDateTimeString())->toBe('2026-09-01 08:54:00'); }); it('nimmt ohne Registrierungsbestellung das Aktivierungsdatum (Admin-Freischaltung)', function () { $user = ppaMakeUser(['payment_account' => '2027-08-28 00:00:00']); UserUtil::backfillPartnerPaidAt(); expect($user->fresh()->partner_paid_at->toDateTimeString())->toBe('2026-08-28 16:13:11'); }); it('laesst bereits gesetzte Werte und unbezahlte Partner unangetastet', function () { $set = ppaMakeUser(['payment_account' => '2027-01-01', 'partner_paid_at' => '2025-03-03 12:00:00']); $unpaid = ppaMakeUser(); expect(UserUtil::backfillPartnerPaidAt())->toBe(0) ->and($set->fresh()->partner_paid_at->toDateTimeString())->toBe('2025-03-03 12:00:00') ->and($unpaid->fresh()->partner_paid_at)->toBeNull(); }); it('ignoriert unbezahlte Registrierungsbestellungen', function () { // Abgelaufener Zahlungslink, danach ein zweiter erfolgreicher: nur die // bezahlte Bestellung zaehlt. $user = ppaMakeUser(['payment_account' => '2027-09-01 06:49:35']); ppaRegistrationOrder($user, false); $paid = ppaRegistrationOrder($user); UserSalesVolume::forceCreate([ 'user_id' => $user->id, 'shopping_order_id' => $paid->id, 'month' => 9, 'year' => 2026, 'points' => 200, 'status' => 1, 'created_at' => '2026-09-01 06:49:35', ]); UserUtil::backfillPartnerPaidAt(); expect($user->fresh()->partner_paid_at->toDateString())->toBe('2026-09-01'); }); });