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'); });