'u-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', ]); } function makePointsProduct(float $points): int { return (int) DB::table('products')->insertGetId([ 'name' => 'P-'.uniqid('', true), 'title' => 'Produkt', 'active' => true, 'price' => 119, 'tax' => 19, 'weight' => 500, 'points' => $points, 'show_on' => json_encode(['2']), 'created_at' => now(), 'updated_at' => now(), ]); } function makePointsOrder(float $points, int $userId = 1): int { return (int) DB::table('shopping_orders')->insertGetId([ 'shopping_user_id' => 1, 'country_id' => 1, 'user_shop_id' => 1, 'auth_user_id' => $userId, 'points' => $points, 'created_at' => now(), 'updated_at' => now(), ]); } function makePointsItem(int $orderId, int $productId, float $points, int $qty = 1, bool $addon = true): int { return (int) DB::table('shopping_order_items')->insertGetId([ 'shopping_order_id' => $orderId, 'product_id' => $productId, 'qty' => $qty, 'points' => $points, 'is_abo_addon' => $addon, 'created_at' => now(), 'updated_at' => now(), ]); } function makePointsVolume(int $userId, int $orderId, float $points, int $status = 1): int { return (int) DB::table('user_sales_volumes')->insertGetId([ 'user_id' => $userId, 'shopping_order_id' => $orderId, 'month' => (int) now()->month, 'year' => (int) now()->year, 'date' => now()->format('d.m.Y'), 'points' => $points, 'total_net' => 100, 'status_points' => 1, 'status' => $status, 'created_at' => now(), 'updated_at' => now(), ]); } describe('DecimalPointsRepairService', function () { beforeEach(function () { $this->service = new DecimalPointsRepairService; $this->month = (int) now()->month; $this->year = (int) now()->year; }); it('zieht abgeschnittene Abo-Einmalartikel nach', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); $item = makePointsItem($order, $product, 7.0); $volume = makePointsVolume($user->id, $order, 7.0); $result = $this->service->repair($this->month, $this->year, false); expect($result['total_delta'])->toBe(0.5) ->and((float) DB::table('shopping_order_items')->where('id', $item)->value('points'))->toBe(7.5) ->and((float) DB::table('shopping_orders')->where('id', $order)->value('points'))->toBe(7.5) ->and((float) DB::table('user_sales_volumes')->where('id', $volume)->value('points'))->toBe(7.5); }); it('rechnet das Delta mit der Menge hoch', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(21.0, $user->id); makePointsItem($order, $product, 7.0, 3); $volume = makePointsVolume($user->id, $order, 21.0); $this->service->repair($this->month, $this->year, false); expect((float) DB::table('shopping_orders')->where('id', $order)->value('points'))->toBe(22.5) ->and((float) DB::table('user_sales_volumes')->where('id', $volume)->value('points'))->toBe(22.5); }); it('zieht die Monatssummen nach', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); makePointsItem($order, $product, 7.0); $volume = makePointsVolume($user->id, $order, 7.0); $this->service->repair($this->month, $this->year, false); $row = DB::table('user_sales_volumes')->where('id', $volume)->first(); expect((float) $row->month_KP_points)->toBe(7.5) ->and((float) $row->month_TP_points)->toBe(7.5); }); it('korrigiert gerundete Sammelbestellungen nach unten', function () { $user = makePointsUser(); $order = makePointsOrder(240.0, $user->id); $volume = makePointsVolume($user->id, $order, 240.0); DB::table('shopping_collect_orders')->insert([ 'user_id' => $user->id, 'shopping_order_id' => $order, 'points' => 240, 'qty_total' => 2, 'shop_items' => json_encode([ ['points_total' => 120.0], ['points_total' => 119.5], ]), 'created_at' => now(), 'updated_at' => now(), ]); $result = $this->service->repair($this->month, $this->year, false); expect($result['total_delta'])->toBe(-0.5) ->and((float) DB::table('shopping_orders')->where('id', $order)->value('points'))->toBe(239.5) ->and((float) DB::table('user_sales_volumes')->where('id', $volume)->value('points'))->toBe(239.5); }); it('spiegelt das Delta bei Storno-Zeilen', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); makePointsItem($order, $product, 7.0); $original = makePointsVolume($user->id, $order, 7.0); $storno = makePointsVolume($user->id, $order, -7.0, 6); $this->service->repair($this->month, $this->year, false); expect((float) DB::table('user_sales_volumes')->where('id', $original)->value('points'))->toBe(7.5) ->and((float) DB::table('user_sales_volumes')->where('id', $storno)->value('points'))->toBe(-7.5); }); it('lässt Gratis-Positionen mit 0 Punkten unangetastet', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(0.0, $user->id); $item = makePointsItem($order, $product, 0.0); makePointsVolume($user->id, $order, 0.0); $result = $this->service->repair($this->month, $this->year, false); expect($result['orders'])->toBe([]) ->and((float) DB::table('shopping_order_items')->where('id', $item)->value('points'))->toBe(0.0); }); it('lässt reguläre (nicht-Addon) Positionen unangetastet', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); $item = makePointsItem($order, $product, 7.0, 1, false); makePointsVolume($user->id, $order, 7.0); $result = $this->service->repair($this->month, $this->year, false); expect($result['orders'])->toBe([]) ->and((float) DB::table('shopping_order_items')->where('id', $item)->value('points'))->toBe(7.0); }); it('lässt Positionen mit ganzzahligen Produktpunkten unangetastet', function () { $user = makePointsUser(); $product = makePointsProduct(14.0); $order = makePointsOrder(14.0, $user->id); makePointsItem($order, $product, 14.0); makePointsVolume($user->id, $order, 14.0); expect($this->service->repair($this->month, $this->year, false)['orders'])->toBe([]); }); it('fasst andere Monate nicht an', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); $item = makePointsItem($order, $product, 7.0); DB::table('user_sales_volumes')->insert([ 'user_id' => $user->id, 'shopping_order_id' => $order, 'month' => (int) now()->subMonthNoOverflow()->month, 'year' => (int) now()->subMonthNoOverflow()->year, 'date' => now()->subMonthNoOverflow()->format('d.m.Y'), 'points' => 7.0, 'total_net' => 100, 'status_points' => 1, 'status' => 1, 'created_at' => now(), 'updated_at' => now(), ]); $result = $this->service->repair($this->month, $this->year, false); expect($result['orders'])->toBe([]) ->and((float) DB::table('shopping_order_items')->where('id', $item)->value('points'))->toBe(7.0); }); it('schreibt im Trockenlauf nichts', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); $item = makePointsItem($order, $product, 7.0); $volume = makePointsVolume($user->id, $order, 7.0); $result = $this->service->repair($this->month, $this->year, true); expect($result['total_delta'])->toBe(0.5) ->and((float) DB::table('shopping_order_items')->where('id', $item)->value('points'))->toBe(7.0) ->and((float) DB::table('shopping_orders')->where('id', $order)->value('points'))->toBe(7.0) ->and((float) DB::table('user_sales_volumes')->where('id', $volume)->value('points'))->toBe(7.0); }); it('ist idempotent', function () { $user = makePointsUser(); $product = makePointsProduct(7.5); $order = makePointsOrder(7.0, $user->id); makePointsItem($order, $product, 7.0); $volume = makePointsVolume($user->id, $order, 7.0); $this->service->repair($this->month, $this->year, false); $second = $this->service->repair($this->month, $this->year, false); expect($second['orders'])->toBe([]) ->and((float) DB::table('user_sales_volumes')->where('id', $volume)->value('points'))->toBe(7.5); }); })->group('points'); describe('points:repair-decimal Command', function () { it('verweigert abgeschlossene Monate ohne --allow-closed-month', function () { $past = now()->subMonthNoOverflow(); $this->artisan('points:repair-decimal', [ '--month' => $past->month, '--year' => $past->year, '--force' => true, ])->assertExitCode(1); }); it('erlaubt den Trockenlauf für abgeschlossene Monate', function () { $past = now()->subMonthNoOverflow(); $this->artisan('points:repair-decimal', [ '--month' => $past->month, '--year' => $past->year, ])->assertExitCode(0); }); it('läuft für den laufenden Monat', function () { $this->artisan('points:repair-decimal')->assertExitCode(0); }); })->group('points');