mivita/tests/Feature/AboOneTimeDecimalPointsTest.php
Kevin a21bebc932 Punkte: Dezimalpunkte in Einmalprodukten, Homeparty und Shop-API nicht mehr abschneiden
Produkte mit 7,5 Punkten wurden an drei Stellen zu ganzen Zahlen:
user_abo_one_time_items (PHP-Cast int, 7,5 -> 7), homeparty_user_order_items
(MySQL rundete auf 8) und shopping_collect_orders (Summe der Shop-API).
Die Migration zieht die drei Spalten auf DECIMAL(10,2) nach (Nachtrag zu
2026_01_22), Cast und Abo-Snapshot rechnen mit float.

points:report-decimal-diff wertet die Differenzen je Berater und Monat aus
(nur lesend), points:repair-decimal korrigiert den laufenden Monat und
zieht die Monatssummen nach.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:42:27 +00:00

84 lines
2.9 KiB
PHP

<?php
/**
* Dezimal-Punkte bei Abo-Einmalartikeln.
*
* Produkte können halbe Punkte haben (z.B. 7,5). Der Snapshot in
* {@see AboOrderCart::buildOneTimeSnapshot()} hat diese früher per (int)-Cast
* abgeschnitten (7,5 -> 7); zusätzlich war user_abo_one_time_items.points noch
* eine INT-Spalte. Die Punkte fließen über shopping_orders.points in
* user_sales_volumes und damit in die Provisionsberechnung ein, der Verlust war
* also nicht nur ein Anzeigefehler auf der Rechnung.
*/
use App\Models\Product;
use App\Models\UserAboOneTimeItem;
use App\Services\AboOneTimeService;
use App\Services\AboOrderCart;
use App\Services\Facade\Yard;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
/** Setzt die Punkte am Produkt an der Datenbank vorbei am Mutator-freien Weg. */
function makeProductWithPoints(array $showOn, float $points): Product
{
$product = makeProduct($showOn);
DB::table('products')->where('id', $product->id)->update(['points' => $points]);
return Product::findOrFail($product->id);
}
describe('Abo-Einmalartikel mit Dezimal-Punkten', function () {
beforeEach(function () {
makeShopEnv();
$this->service = new AboOneTimeService;
});
it('behält halbe Punkte im Preis-Snapshot', function () {
$abo = makeMeAbo();
$product = makeProductWithPoints(['2'], 7.5);
$snapshot = AboOrderCart::buildOneTimeSnapshot($product, $abo);
expect($snapshot['points'])->toBe(7.5);
});
it('persistiert halbe Punkte am Einmal-Artikel', function () {
$abo = makeMeAbo();
$product = makeProductWithPoints(['2'], 7.5);
$message = $this->service->handleAction($abo, ['action' => 'add', 'product_id' => $product->id]);
expect($message)->toBeNull();
$item = UserAboOneTimeItem::where('user_abo_id', $abo->id)->firstOrFail();
expect((float) $item->points)->toBe(7.5)
->and((float) DB::table('user_abo_one_time_items')->where('id', $item->id)->value('points'))->toBe(7.5);
});
it('übernimmt halbe Punkte in den Abo-Yard', function () {
$abo = makeMeAbo();
$product = makeProductWithPoints(['2'], 7.5);
$this->service->handleAction($abo, ['action' => 'add', 'product_id' => $product->id]);
$item = UserAboOneTimeItem::where('user_abo_id', $abo->id)->firstOrFail();
$item->update(['qty' => 2, 'confirmed_qty' => 2, 'confirmed_at' => now()]);
AboOrderCart::addOneTimeItemsToYard($abo->fresh());
expect(Yard::instance(AboOrderCart::INSTANCE)->points())->toBe(15.0);
});
it('lässt ganzzahlige Punkte unverändert', function () {
$abo = makeMeAbo();
$product = makeProductWithPoints(['2'], 14.0);
$snapshot = AboOrderCart::buildOneTimeSnapshot($product, $abo);
expect($snapshot['points'])->toBe(14.0);
});
})->group('abo');