Carbon::setTestNow('2026-09-10 12:00:00')); afterEach(fn () => Carbon::setTestNow()); function addonUser(?int $sponsor = null): User { return User::forceCreate([ 'email' => 'addon-'.uniqid().'@example.test', 'password' => bcrypt('secret'), 'lang' => 'de', 'admin' => 0, 'm_level' => 1, 'm_sponsor' => $sponsor, 'active_date' => '2026-01-01', 'payment_account' => '2027-01-01', ]); } /** * Abo-Bestellung mit optionalem Einmal-Zusatz und der zugehoerigen Umsatzbuchung. */ function addonOrder(User $member, float $points, float $addonPoints = 0.0): ShoppingOrder { $order = ShoppingOrder::create([ 'shopping_user_id' => 0, 'auth_user_id' => $member->id, 'member_id' => $member->id, 'country_id' => 1, 'language' => 'de', 'user_shop_id' => 1, 'payment_for' => 6, 'total' => $points, 'subtotal' => $points, 'points' => $points, 'paid' => true, 'shipped' => 0, 'is_abo' => true, 'mode' => 'test', ]); if ($addonPoints > 0) { ShoppingOrderItem::forceCreate([ 'shopping_order_id' => $order->id, 'product_id' => 0, 'qty' => 1, 'price' => $addonPoints, 'points' => $addonPoints, 'is_abo_addon' => true, ]); } return $order; } function addonVolume(User $member, float $points, ShoppingOrder $order): UserSalesVolume { return UserSalesVolume::forceCreate([ 'user_id' => $member->id, 'shopping_order_id' => $order->id, 'month' => 9, 'year' => 2026, 'date' => '2026-09-01', 'status' => 2, 'status_points' => 1, 'status_turnover' => null, 'points' => $points, 'total_net' => $points, ]); } function addonRankingRow(User $root): array { return app(BackofficeDashboardService::class)->overview($root, 9, 2026)['top_customer_abos']['by_points'][0]; } it('weist die Zusatzpunkte einer stornierten Bestellung nicht mehr aus', function () { $root = addonUser(); $member = addonUser($root->id); $storniert = addonOrder($member, 100.0, 20.0); SalesPointsVolume::cancelSalesPointsVolume(addonVolume($member, 100.0, $storniert), 9911); addonVolume($member, 100.0, addonOrder($member, 100.0)); $row = addonRankingRow($root); expect($row['points_actual'])->toBe(100.0) ->and($row['points_addon'])->toBe(0.0) ->and($row['points_recurring'])->toBe(100.0); }); it('weist die Zusatzpunkte einer nicht stornierten Bestellung unveraendert aus', function () { $root = addonUser(); $member = addonUser($root->id); addonVolume($member, 100.0, addonOrder($member, 100.0, 20.0)); $row = addonRankingRow($root); expect($row['points_actual'])->toBe(100.0) ->and($row['points_addon'])->toBe(20.0) ->and($row['points_recurring'])->toBe(80.0); }); it('rechnet einen Teilstorno anteilig auf die Zusatzpunkte an', function () { $root = addonUser(); $member = addonUser($root->id); $order = addonOrder($member, 100.0, 20.0); addonVolume($member, 100.0, $order); addonVolume($member, -50.0, $order)->update(['status' => 6, 'status_turnover' => 2, 'storno_origin_status' => 2]); $row = addonRankingRow($root); expect($row['points_actual'])->toBe(50.0) ->and($row['points_addon'])->toBe(10.0) ->and($row['points_recurring'])->toBe(40.0); });