April 2026 waren Wirtschaft Feedback
This commit is contained in:
parent
02f2a4c23e
commit
9ce711d6b2
167 changed files with 25278 additions and 8518 deletions
345
tests/Feature/ProductionPhase5Test.php
Normal file
345
tests/Feature/ProductionPhase5Test.php
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\PackagingItem;
|
||||
use App\Models\PackagingMaterial;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductIngredient;
|
||||
use App\Models\StockEntry;
|
||||
use App\Models\Supplier;
|
||||
use App\Services\ProductionService;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function phase5MakeUser(int $adminLevel = 1): User
|
||||
{
|
||||
$user = User::query()->create([
|
||||
'email' => uniqid('p5_', true).'@test.example',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
$user->forceFill([
|
||||
'admin' => $adminLevel,
|
||||
'confirmed' => true,
|
||||
'active' => true,
|
||||
'wizard' => 100,
|
||||
'blocked' => false,
|
||||
])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
function phase5EnsureCountry(): Country
|
||||
{
|
||||
return Country::query()->firstOrCreate(
|
||||
['code' => 'DE'],
|
||||
[
|
||||
'phone' => '00',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Germany',
|
||||
'fr' => 'Germany',
|
||||
'it' => 'Germany',
|
||||
'ru' => 'Germany',
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
test('production service stores ingredients and packaging snapshot', function () {
|
||||
phase5EnsureCountry();
|
||||
$user = phase5MakeUser(7);
|
||||
$location = Location::factory()->create();
|
||||
$supplier = Supplier::factory()->create();
|
||||
|
||||
$ing = Ingredient::query()->create([
|
||||
'name' => 'P5-Rohstoff',
|
||||
'trans_name' => '',
|
||||
'inci' => 'INCI',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]);
|
||||
|
||||
$product = Product::query()->create([
|
||||
'name' => 'P5-Produkt',
|
||||
'title' => 'P5-Produkt',
|
||||
'active' => true,
|
||||
'shelf_life_type' => null,
|
||||
]);
|
||||
|
||||
ProductIngredient::query()->create([
|
||||
'product_id' => $product->id,
|
||||
'ingredient_id' => $ing->id,
|
||||
'pos' => 0,
|
||||
'gram' => 10,
|
||||
'factor' => 1.0,
|
||||
]);
|
||||
|
||||
$material = PackagingMaterial::factory()->create();
|
||||
$pk = PackagingItem::factory()->create([
|
||||
'packaging_material_id' => $material->id,
|
||||
'supplier_id' => $supplier->id,
|
||||
'name' => 'P5-Flasche',
|
||||
'category' => 'packaging',
|
||||
]);
|
||||
$product->packagings()->sync([
|
||||
$pk->id => ['quantity' => 2, 'pos' => 0],
|
||||
]);
|
||||
|
||||
$stock = StockEntry::query()->create([
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => $ing->id,
|
||||
'packaging_item_id' => null,
|
||||
'supplier_id' => $supplier->id,
|
||||
'location_id' => $location->id,
|
||||
'unit' => 'gram',
|
||||
'ordered_by' => $user->id,
|
||||
'ordered_at' => '2026-01-01',
|
||||
'ordered_quantity' => 10000,
|
||||
'price_per_kg' => 1,
|
||||
'status' => 'received',
|
||||
'received_by' => $user->id,
|
||||
'received_at' => '2026-01-15',
|
||||
'received_quantity' => 10000,
|
||||
'batch_number' => 'B-P5',
|
||||
'best_before' => '2030-12-31',
|
||||
'quality_id' => null,
|
||||
]);
|
||||
|
||||
$service = app(ProductionService::class);
|
||||
$production = $service->store(
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'location_id' => $location->id,
|
||||
'produced_at' => '2026-03-01',
|
||||
'quantity' => 5,
|
||||
'notes' => 'test',
|
||||
],
|
||||
[
|
||||
[
|
||||
'ingredient_id' => $ing->id,
|
||||
'stock_entry_id' => $stock->id,
|
||||
'quantity_used' => '50',
|
||||
],
|
||||
],
|
||||
$user->id
|
||||
);
|
||||
|
||||
expect($production->productionIngredients)->toHaveCount(1);
|
||||
expect((float) $production->productionIngredients->first()->quantity_used)->toBe(50.0);
|
||||
expect($production->productionPackagings)->toHaveCount(1);
|
||||
expect($production->productionPackagings->first()->quantity_used)->toBe(10);
|
||||
});
|
||||
|
||||
test('production service rejects wrong gram sum', function () {
|
||||
phase5EnsureCountry();
|
||||
$user = phase5MakeUser(7);
|
||||
$location = Location::factory()->create();
|
||||
$supplier = Supplier::factory()->create();
|
||||
|
||||
$ing = Ingredient::query()->create([
|
||||
'name' => 'P5-R2',
|
||||
'trans_name' => '',
|
||||
'inci' => 'X',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]);
|
||||
|
||||
$product = Product::query()->create([
|
||||
'name' => 'P5-P2',
|
||||
'title' => 'P5-P2',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
ProductIngredient::query()->create([
|
||||
'product_id' => $product->id,
|
||||
'ingredient_id' => $ing->id,
|
||||
'pos' => 0,
|
||||
'gram' => 10,
|
||||
'factor' => 1.0,
|
||||
]);
|
||||
|
||||
$stock = StockEntry::query()->create([
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => $ing->id,
|
||||
'packaging_item_id' => null,
|
||||
'supplier_id' => $supplier->id,
|
||||
'location_id' => $location->id,
|
||||
'unit' => 'gram',
|
||||
'ordered_by' => $user->id,
|
||||
'ordered_at' => '2026-01-01',
|
||||
'ordered_quantity' => 10000,
|
||||
'price_per_kg' => 1,
|
||||
'status' => 'received',
|
||||
'received_by' => $user->id,
|
||||
'received_at' => '2026-01-15',
|
||||
'received_quantity' => 10000,
|
||||
'batch_number' => 'B2',
|
||||
'best_before' => '2030-12-31',
|
||||
'quality_id' => null,
|
||||
]);
|
||||
|
||||
$service = app(ProductionService::class);
|
||||
|
||||
expect(fn () => $service->store(
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'location_id' => $location->id,
|
||||
'produced_at' => '2026-03-01',
|
||||
'quantity' => 5,
|
||||
'notes' => null,
|
||||
],
|
||||
[
|
||||
[
|
||||
'ingredient_id' => $ing->id,
|
||||
'stock_entry_id' => $stock->id,
|
||||
'quantity_used' => '100',
|
||||
],
|
||||
],
|
||||
$user->id
|
||||
))->toThrow(ValidationException::class);
|
||||
});
|
||||
|
||||
test('production api recipe json returns ingredients and packagings', function () {
|
||||
phase5EnsureCountry();
|
||||
$user = phase5MakeUser(1);
|
||||
$location = Location::factory()->create();
|
||||
$supplier = Supplier::factory()->create();
|
||||
|
||||
$ing = Ingredient::query()->create([
|
||||
'name' => 'P5-API',
|
||||
'trans_name' => '',
|
||||
'inci' => 'Y',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]);
|
||||
|
||||
$product = Product::query()->create([
|
||||
'name' => 'P5-API-P',
|
||||
'title' => 'P5-API-P',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
ProductIngredient::query()->create([
|
||||
'product_id' => $product->id,
|
||||
'ingredient_id' => $ing->id,
|
||||
'pos' => 0,
|
||||
'gram' => 2,
|
||||
'factor' => 1.5,
|
||||
]);
|
||||
|
||||
StockEntry::query()->create([
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => $ing->id,
|
||||
'packaging_item_id' => null,
|
||||
'supplier_id' => $supplier->id,
|
||||
'location_id' => $location->id,
|
||||
'unit' => 'gram',
|
||||
'ordered_by' => $user->id,
|
||||
'ordered_at' => '2026-01-01',
|
||||
'ordered_quantity' => 5000,
|
||||
'price_per_kg' => 1,
|
||||
'status' => 'received',
|
||||
'received_by' => $user->id,
|
||||
'received_at' => '2026-02-01',
|
||||
'received_quantity' => 5000,
|
||||
'batch_number' => 'API-1',
|
||||
'best_before' => '2028-06-01',
|
||||
'quality_id' => null,
|
||||
]);
|
||||
|
||||
$this->actingAs($user, 'user');
|
||||
$response = $this->getJson(route('admin.inventory.api.products.recipe', ['product' => $product->id]).'?location_id='.$location->id.'&quantity=10');
|
||||
$response->assertSuccessful();
|
||||
$response->assertJsonPath('product.id', $product->id);
|
||||
expect((float) $response->json('ingredients.0.required_grams_total'))->toBe(30.0);
|
||||
});
|
||||
|
||||
test('production mhd warning when stock expires before product end', function () {
|
||||
phase5EnsureCountry();
|
||||
$user = phase5MakeUser(7);
|
||||
$location = Location::factory()->create();
|
||||
$supplier = Supplier::factory()->create();
|
||||
|
||||
$ing = Ingredient::query()->create([
|
||||
'name' => 'P5-MHD',
|
||||
'trans_name' => '',
|
||||
'inci' => 'Z',
|
||||
'trans_inci' => '',
|
||||
'effect' => '',
|
||||
'trans_effect' => '',
|
||||
'active' => true,
|
||||
'pos' => 0,
|
||||
]);
|
||||
|
||||
$product = Product::query()->create([
|
||||
'name' => 'P5-MHD-P',
|
||||
'title' => 'P5-MHD-P',
|
||||
'active' => true,
|
||||
'shelf_life_type' => 'fixed',
|
||||
'shelf_life_months' => 24,
|
||||
]);
|
||||
|
||||
ProductIngredient::query()->create([
|
||||
'product_id' => $product->id,
|
||||
'ingredient_id' => $ing->id,
|
||||
'pos' => 0,
|
||||
'gram' => 1,
|
||||
'factor' => 1.0,
|
||||
]);
|
||||
|
||||
$stock = StockEntry::query()->create([
|
||||
'entry_type' => 'ingredient',
|
||||
'ingredient_id' => $ing->id,
|
||||
'packaging_item_id' => null,
|
||||
'supplier_id' => $supplier->id,
|
||||
'location_id' => $location->id,
|
||||
'unit' => 'gram',
|
||||
'ordered_by' => $user->id,
|
||||
'ordered_at' => '2026-01-01',
|
||||
'ordered_quantity' => 5000,
|
||||
'price_per_kg' => 1,
|
||||
'status' => 'received',
|
||||
'received_by' => $user->id,
|
||||
'received_at' => '2026-02-01',
|
||||
'received_quantity' => 5000,
|
||||
'batch_number' => 'MHD-B',
|
||||
'best_before' => '2026-06-01',
|
||||
'quality_id' => null,
|
||||
]);
|
||||
|
||||
$service = app(ProductionService::class);
|
||||
$production = $service->store(
|
||||
[
|
||||
'product_id' => $product->id,
|
||||
'location_id' => $location->id,
|
||||
'produced_at' => '2026-03-01',
|
||||
'quantity' => 1,
|
||||
'notes' => null,
|
||||
],
|
||||
[
|
||||
[
|
||||
'ingredient_id' => $ing->id,
|
||||
'stock_entry_id' => $stock->id,
|
||||
'quantity_used' => '1',
|
||||
],
|
||||
],
|
||||
$user->id
|
||||
);
|
||||
|
||||
expect($production->mhd_warning)->toBeTrue();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue