Warenwirtschaft: F-08 — Füllgewicht mit Nachkommastellen
fill_weight decimal(10,3) + float-Cast; deutsche Eingabe normalisiert, positiv validiert, Fehler im Formular sichtbar. Tests für F-07/F-08. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
fd1572df35
commit
2796b1103e
5 changed files with 114 additions and 2 deletions
|
|
@ -100,8 +100,17 @@ class ProductController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
// F-08: Füllgewicht deutsch normalisieren („39,5“) und positiv validieren.
|
||||
if (Request::has('fill_weight')) {
|
||||
$fillRaw = trim((string) Request::input('fill_weight'));
|
||||
$fillNormalized = $fillRaw === '' ? null : reFormatWawiNumber($fillRaw);
|
||||
// Unbrauchbare Eingabe („abc“) nicht zu leer normalisieren, sonst greift nullable.
|
||||
Request::merge(['fill_weight' => $fillNormalized === '' || $fillNormalized === false ? $fillRaw : $fillNormalized]);
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
'fill_weight' => ['nullable', 'numeric', 'gt:0'],
|
||||
];
|
||||
/*if(isset($data['number']) && $data['number'] != ""){
|
||||
$rules['number'] = 'int';
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ class Product extends Model
|
|||
'show_on' => 'array',
|
||||
'action' => 'array',
|
||||
'wp_number' => 'int',
|
||||
'fill_weight' => 'int',
|
||||
'fill_weight' => 'float',
|
||||
'production_relevant' => 'bool',
|
||||
'single_commission' => 'bool',
|
||||
'amount_commission' => 'bool',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Prüfung 08.09.2026 F-08: Füllgewicht mit Nachkommastellen (z. B. 50 ml × 0,79 = 39,5 g).
|
||||
* Als Integer wurde 39,5 zu 39 und die gesamte Rezepturskalierung wich ab.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->decimal('fill_weight', 10, 3)->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->integer('fill_weight')->nullable()->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -532,7 +532,10 @@
|
|||
</div>
|
||||
<div class="form-group col-sm-3">
|
||||
<label class="form-label" for="fill_weight">{{ __('Füllgewicht (g) – für Produktion') }}</label>
|
||||
{{ Form::text('fill_weight', $product->fill_weight, ['placeholder' => __('leer = Menge = Gramm (1 g/ml)'), 'class' => 'form-control', 'id' => 'fill_weight']) }}
|
||||
{{ Form::text('fill_weight', $product->fill_weight !== null ? rtrim(rtrim(number_format((float) $product->fill_weight, 3, ',', '.'), '0'), ',') : null, ['placeholder' => __('leer = Menge = Gramm (1 g/ml)'), 'class' => 'form-control'.($errors->has('fill_weight') ? ' is-invalid' : ''), 'id' => 'fill_weight', 'inputmode' => 'decimal']) }}
|
||||
@error('fill_weight')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<small class="form-text text-muted">{{ __('Basis für die Rezeptur-Mengenberechnung (Prozent × Füllgewicht).') }}</small>
|
||||
</div>
|
||||
<div class="form-group col-sm-2">
|
||||
|
|
|
|||
74
tests/Feature/ProductFillWeightTest.php
Normal file
74
tests/Feature/ProductFillWeightTest.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Location;
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\PackagingMaterial;
|
||||
use App\Models\Product;
|
||||
use App\Models\Supplier;
|
||||
use App\Services\ProductionService;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* Prüfung 08.09.2026 F-08: Füllgewicht mit Nachkommastellen (Dezimalspalte, deutsche Eingabe, Validierung).
|
||||
*/
|
||||
function fwUser(): User
|
||||
{
|
||||
$user = User::query()->create(['email' => uniqid('fw_', true).'@test.example', 'password' => bcrypt('password')]);
|
||||
$user->forceFill(['admin' => 7, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
test('F-08: füllgewicht 39,5 wird mit nachkommastellen gespeichert und skaliert die rezeptur exakt', function () {
|
||||
$this->actingAs(fwUser(), 'user');
|
||||
|
||||
$this->post(route('admin_product_store'), ['id' => 'new', 'name' => 'FW-Dezimal', 'fill_weight' => '39,5'])
|
||||
->assertRedirect()->assertSessionHasNoErrors();
|
||||
|
||||
$product = Product::query()->where('name', 'FW-Dezimal')->firstOrFail();
|
||||
expect($product->fill_weight)->toBe(39.5)
|
||||
->and(app(ProductionService::class)->recipeGramScale($product))->toBe(0.395);
|
||||
|
||||
// Punkt mit 1–2 Nachkommastellen gilt als Dezimaltrenner (WaWi-Konvention), leer = null.
|
||||
$this->post(route('admin_product_store'), ['id' => $product->id, 'name' => 'FW-Dezimal', 'fill_weight' => '4.5'])->assertSessionHasNoErrors();
|
||||
expect($product->fresh()->fill_weight)->toBe(4.5);
|
||||
|
||||
$this->post(route('admin_product_store'), ['id' => $product->id, 'name' => 'FW-Dezimal', 'fill_weight' => ''])->assertSessionHasNoErrors();
|
||||
expect($product->fresh()->fill_weight)->toBeNull();
|
||||
});
|
||||
|
||||
test('F-08: ungültiges oder negatives füllgewicht wird abgelehnt', function () {
|
||||
$this->actingAs(fwUser(), 'user');
|
||||
$product = Product::query()->create(['name' => 'FW-Invalid', 'title' => 'x', 'active' => true, 'fill_weight' => 10]);
|
||||
|
||||
// Der Produkt-Controller rendert bei Fehlern das Formular direkt (kein Redirect).
|
||||
foreach (['abc', '-5', '0'] as $bad) {
|
||||
$this->post(route('admin_product_store'), ['id' => $product->id, 'name' => 'FW-Invalid', 'fill_weight' => $bad])
|
||||
->assertOk()
|
||||
->assertSee('is-invalid', false);
|
||||
}
|
||||
expect($product->fresh()->fill_weight)->toBe(10.0);
|
||||
});
|
||||
|
||||
test('F-07: produktionsformular liefert die verpackungs-stückliste je ergebnisprodukt für die vorschau', function () {
|
||||
$this->actingAs(fwUser(), 'user');
|
||||
$supplier = Supplier::factory()->create();
|
||||
$material = PackagingMaterial::factory()->create();
|
||||
$pk = PackagingItem::factory()->create(['packaging_material_id' => $material->id, 'supplier_id' => $supplier->id, 'name' => 'F07-Tiegel', 'category' => 'packaging']);
|
||||
$small = Product::query()->create(['name' => 'F07-5ml', 'title' => 'x', 'active' => true, 'no_recipe_required' => true, 'production_relevant' => false]);
|
||||
$small->packagings()->sync([$pk->id => ['quantity' => 2, 'pos' => 0]]);
|
||||
Location::factory()->create();
|
||||
|
||||
$html = $this->get(route('admin.inventory.productions.create'))->assertOk()->getContent();
|
||||
$start = strpos($html, "data-packaging-bom='") + strlen("data-packaging-bom='");
|
||||
$bom = json_decode(substr($html, $start, strpos($html, "'", $start) - $start), true);
|
||||
|
||||
expect($bom)->toHaveKey((string) $small->id)
|
||||
->and($bom[$small->id][0]['name'])->toBe('F07-Tiegel')
|
||||
->and($bom[$small->id][0]['per_unit'])->toEqual(2)
|
||||
->and($html)->toContain('renderPackagingPreview');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue