gruene-seele/tests/Feature/InventoryPhase3Test.php

233 lines
7.5 KiB
PHP

<?php
use App\Models\Country;
use App\Models\Ingredient;
use App\Models\Location;
use App\Models\MaterialQuality;
use App\Models\StockEntry;
use App\Models\Supplier;
use App\Repositories\StockEntryRepository;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function phase3MakeUser(int $adminLevel): User
{
$user = User::query()->create([
'email' => uniqid('inv3_', true).'@test.example',
'password' => bcrypt('password'),
]);
$user->forceFill([
'admin' => $adminLevel,
'confirmed' => true,
'active' => true,
'wizard' => 100,
'blocked' => false,
])->save();
return $user->fresh();
}
function phase3EnsureCountry(): Country
{
return Country::query()->firstOrCreate(
['code' => 'DE'],
[
'phone' => '00',
'en' => 'Germany',
'de' => 'Deutschland',
'es' => 'Germany',
'fr' => 'Germany',
'it' => 'Germany',
'ru' => 'Germany',
'active' => true,
]
);
}
function phase3MakeIngredient(): Ingredient
{
return Ingredient::query()->create([
'name' => 'Test-INCI-'.uniqid('', true),
'trans_name' => '',
'inci' => '',
'trans_inci' => '',
'effect' => '',
'trans_effect' => '',
'active' => true,
'pos' => 0,
]);
}
test('copyreader can list stock entries and receive but not create form', function () {
$user = phase3MakeUser(1);
$this->actingAs($user, 'user');
$this->get(route('admin.inventory.stock-entries.index'))->assertSuccessful();
$this->get(route('admin.inventory.stock-entries.create'))->assertRedirect(route('home'));
});
test('non-copyreader cannot access stock entries', function () {
$user = phase3MakeUser(0);
$this->actingAs($user, 'user');
$this->get(route('admin.inventory.stock-entries.index'))->assertRedirect(route('home'));
});
test('admin creates pending stock entry with unit gram', function () {
phase3EnsureCountry();
$admin = phase3MakeUser(7);
$ingredient = phase3MakeIngredient();
$location = Location::factory()->create();
$supplier = Supplier::factory()->create();
$quality = MaterialQuality::factory()->create();
$this->actingAs($admin, 'user');
$this->post(route('admin.inventory.stock-entries.store'), [
'entry_type' => 'ingredient',
'ingredient_id' => (string) $ingredient->id,
'supplier_id' => (string) $supplier->id,
'location_id' => (string) $location->id,
'quality_id' => (string) $quality->id,
'ordered_at' => '2026-03-27',
'ordered_quantity' => '1000',
'price_per_kg' => '12,50',
])->assertRedirect(route('admin.inventory.stock-entries.index'));
$entry = StockEntry::query()->latest('id')->firstOrFail();
expect($entry->status)->toBe('pending');
expect($entry->unit)->toBe('gram');
expect((float) $entry->ordered_quantity)->toBe(1000.0);
});
test('copyreader can book receive for pending entry', function () {
phase3EnsureCountry();
$admin = phase3MakeUser(7);
$copy = phase3MakeUser(1);
$ingredient = phase3MakeIngredient();
$location = Location::factory()->create();
$supplier = Supplier::factory()->create();
$mq = MaterialQuality::query()->create(['name' => 'MQ-Test-'.uniqid('', true), 'pos' => 1]);
$entry = StockEntry::query()->create([
'entry_type' => 'ingredient',
'ingredient_id' => $ingredient->id,
'packaging_item_id' => null,
'supplier_id' => $supplier->id,
'location_id' => $location->id,
'unit' => 'gram',
'ordered_by' => $admin->id,
'ordered_at' => '2026-03-01',
'ordered_quantity' => 500,
'price_per_kg' => 10,
'price_total' => null,
'status' => 'pending',
]);
$this->actingAs($copy, 'user');
$this->put(route('admin.inventory.stock-entries.receive', $entry), [
'received_at' => '2026-03-15',
'received_quantity' => '500',
'batch_number' => 'B-001',
'best_before' => '2027-12-31',
'quality_id' => (string) $mq->id,
])->assertRedirect(route('admin.inventory.stock-entries.show', $entry));
$entry->refresh();
expect($entry->status)->toBe('received');
expect((float) $entry->received_quantity)->toBe(500.0);
expect($entry->received_by)->toBe($copy->id);
});
test('ingredient search returns select2 results shape', function () {
$user = phase3MakeUser(1);
$ingredient = phase3MakeIngredient();
$this->actingAs($user, 'user');
$response = $this->getJson(route('admin.inventory.api.ingredients.search', ['q' => $ingredient->name]));
$response->assertSuccessful();
$response->assertJsonStructure(['results' => [['id', 'text']]]);
expect($response->json('results.0.id'))->toBe($ingredient->id);
});
test('stock entry factory creates valid pending ingredient entry', function () {
phase3EnsureCountry();
$e = StockEntry::factory()->create();
expect($e->status)->toBe('pending');
expect($e->unit)->toBe('gram');
expect($e->ingredient_id)->toBeGreaterThan(0);
});
test('stock entry factory packaging and received states', function () {
phase3EnsureCountry();
MaterialQuality::query()->create(['name' => 'MQ-Factory-'.uniqid('', true), 'pos' => 99]);
$p = StockEntry::factory()->packaging()->create();
expect($p->entry_type)->toBe('packaging');
expect($p->unit)->toBe('piece');
expect($p->price_total)->not->toBeNull();
$r = StockEntry::factory()->received()->create();
expect($r->status)->toBe('received');
expect($r->received_quantity)->not->toBeNull();
});
test('listForIndex sorts pending before received', function () {
phase3EnsureCountry();
$admin = phase3MakeUser(7);
$ingredient = phase3MakeIngredient();
$location = Location::factory()->create();
$supplier = Supplier::factory()->create();
$quality = MaterialQuality::factory()->create();
$this->actingAs($admin, 'user');
$this->post(route('admin.inventory.stock-entries.store'), [
'entry_type' => 'ingredient',
'ingredient_id' => (string) $ingredient->id,
'supplier_id' => (string) $supplier->id,
'location_id' => (string) $location->id,
'quality_id' => (string) $quality->id,
'ordered_at' => '2026-01-01',
'ordered_quantity' => '100',
'price_per_kg' => '1',
]);
$this->post(route('admin.inventory.stock-entries.store'), [
'entry_type' => 'ingredient',
'ingredient_id' => (string) $ingredient->id,
'supplier_id' => (string) $supplier->id,
'location_id' => (string) $location->id,
'quality_id' => (string) $quality->id,
'ordered_at' => '2026-06-01',
'ordered_quantity' => '200',
'price_per_kg' => '1',
]);
$entries = StockEntry::query()->orderBy('id')->get();
expect($entries)->toHaveCount(2);
$older = $entries[0];
$newer = $entries[1];
$this->actingAs($admin, 'user');
$this->put(route('admin.inventory.stock-entries.receive', $older), [
'received_at' => '2026-03-10',
'received_quantity' => '100',
'batch_number' => 'X',
'best_before' => '2028-01-01',
'quality_id' => '',
]);
$repo = app(StockEntryRepository::class);
$list = $repo->listForIndex();
expect($list)->toHaveCount(2);
expect($list[0]->id)->toBe($newer->id);
expect($list[0]->status)->toBe('pending');
expect($list[1]->id)->toBe($older->id);
expect($list[1]->status)->toBe('received');
});