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>
84 lines
2.9 KiB
PHP
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');
|