dstpabuaran.com/tests/Feature/Admin/Manage/CuttingTest.php
Yoga Pangestu e7aa582572 feat: add cutting management functionality with CRUD operations
- Implemented CuttingIndex component for listing and managing cuttings.
- Added routes for cutting management in web.php.
- Created CuttingTest for testing cutting-related features including authorization, validation, and stock management.
- Updated roles create and edit pages to include necessary imports.
- Refactored settings and profile pages to streamline imports.
- Enhanced permissions checks for cutting management actions.
2026-08-04 02:24:11 +07:00

748 lines
24 KiB
PHP

<?php
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingMaterialCombination;
use App\Models\CuttingResult;
use App\Models\RawMaterialPrice;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| HELPERS
|--------------------------------------------------------------------------
*/
function giveCuttingPermissions(User $user): void
{
$seeder = new RolePermissionSeeder;
$seeder->run();
$user->givePermissionTo([
'cutting.view',
'cutting.create',
'cutting.update',
'cutting.delete',
]);
}
function makeValidCuttingPayload(array $overrides = []): array
{
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
return array_merge([
'product_name' => 'Produk Test',
'sample' => 5,
'original_outside_sample' => 10,
'cutting_result' => 15,
'description' => 'Keterangan test',
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 20,
'material_result' => 15,
'combination_index' => null,
],
],
'combinations' => [],
'photo_key' => null,
], $overrides);
}
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.manage.cuttings.index'));
$response->assertRedirect(route('login'));
});
test('guests are redirected when visiting create page', function () {
$response = $this->get(route('admin.manage.cuttings.create'));
$response->assertRedirect(route('login'));
});
test('guests are redirected when visiting edit page', function () {
$cutting = Cutting::factory()->create();
$response = $this->get(route('admin.manage.cuttings.edit', $cutting));
$response->assertRedirect(route('login'));
});
test('guest cannot create cutting', function () {
$response = $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload());
$response->assertRedirect(route('login'));
$this->assertDatabaseCount('cuttings', 0);
});
test('guest cannot update cutting', function () {
$cutting = Cutting::factory()->create();
$response = $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload());
$response->assertRedirect(route('login'));
});
test('guest cannot delete cutting', function () {
$cutting = Cutting::factory()->create();
$response = $this->delete(route('admin.manage.cuttings.destroy', $cutting));
$response->assertRedirect(route('login'));
$this->assertDatabaseHas('cuttings', ['id' => $cutting->id, 'deleted_at' => null]);
});
/*
|--------------------------------------------------------------------------
| AUTHORIZATION
|--------------------------------------------------------------------------
*/
test('user without permission cannot view cuttings', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->get(route('admin.manage.cuttings.index'))->assertForbidden();
});
test('user without permission cannot visit create page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->get(route('admin.manage.cuttings.create'))->assertForbidden();
});
test('user without permission cannot store cutting', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload())->assertForbidden();
$this->assertDatabaseCount('cuttings', 0);
});
test('user without permission cannot visit edit page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$cutting = Cutting::factory()->create();
$this->get(route('admin.manage.cuttings.edit', $cutting))->assertForbidden();
});
test('user without permission cannot update cutting', function () {
$user = User::factory()->create();
$this->actingAs($user);
$cutting = Cutting::factory()->create();
$this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload())->assertForbidden();
});
test('user without permission cannot delete cutting', function () {
$user = User::factory()->create();
$this->actingAs($user);
$cutting = Cutting::factory()->create();
$this->delete(route('admin.manage.cuttings.destroy', $cutting))->assertForbidden();
$this->assertDatabaseHas('cuttings', ['id' => $cutting->id, 'deleted_at' => null]);
});
/*
|--------------------------------------------------------------------------
| INDEX
|--------------------------------------------------------------------------
*/
test('user with permission can view cuttings', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
Cutting::factory()->count(3)->create(['created_by_id' => $user->id]);
$this->get(route('admin.manage.cuttings.index'))->assertOk();
});
/*
|--------------------------------------------------------------------------
| CREATE
|--------------------------------------------------------------------------
*/
test('user with permission can visit create page', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$this->get(route('admin.manage.cuttings.create'))->assertOk();
});
/*
|--------------------------------------------------------------------------
| STORE - VALIDATION
|--------------------------------------------------------------------------
*/
test('store requires product_name', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['product_name']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('product_name');
});
test('store requires sample', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['sample']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('sample');
});
test('store requires original_outside_sample', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['original_outside_sample']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('original_outside_sample');
});
test('store requires cutting_result', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['cutting_result']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('cutting_result');
});
test('store requires at least one material', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload(['materials' => []]);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('materials');
});
test('store requires materials.raw_material_price_id', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['materials'][0]['raw_material_price_id']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('materials.0.raw_material_price_id');
});
test('store requires materials.material_usage', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['materials'][0]['material_usage']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('materials.0.material_usage');
});
test('store requires materials.material_result', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$payload = makeValidCuttingPayload();
unset($payload['materials'][0]['material_result']);
$this->post(route('admin.manage.cuttings.store'), $payload)
->assertSessionHasErrors('materials.0.material_result');
});
/*
|--------------------------------------------------------------------------
| STORE - SUCCESS
|--------------------------------------------------------------------------
*/
test('user with permission can store cutting', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload())
->assertRedirect(route('admin.manage.cuttings.index'));
$this->assertDatabaseHas('cuttings', ['created_by_id' => $user->id]);
$this->assertDatabaseHas('cutting_results', ['product_name' => 'Produk Test']);
});
test('store creates cutting material', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 20,
'material_result' => 15,
'combination_index' => null,
],
],
]))->assertRedirect();
$cutting = Cutting::latest()->first();
$this->assertDatabaseHas('cutting_materials', [
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price->id,
'material_usage' => 20,
'material_result' => 15,
]);
});
test('store deducts stock from raw_material_price', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 30,
'material_result' => 25,
'combination_index' => null,
],
],
]))->assertRedirect();
$price->refresh();
$this->assertEquals(70, $price->stock);
});
test('store with combination creates combination record', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price1 = RawMaterialPrice::factory()->create(['stock' => 100]);
$price2 = RawMaterialPrice::factory()->create(['stock' => 50]);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price1->id,
'material_usage' => 15,
'material_result' => 12,
'combination_index' => 0,
],
[
'raw_material_price_id' => $price2->id,
'material_usage' => 10,
'material_result' => 8,
'combination_index' => 0,
],
],
'combinations' => [
['material_result' => 20],
],
]))->assertRedirect();
$cutting = Cutting::latest()->first();
$this->assertDatabaseHas('cutting_material_combinations', [
'cutting_id' => $cutting->id,
'material_result' => 20,
]);
$combination = CuttingMaterialCombination::where('cutting_id', $cutting->id)->first();
$this->assertDatabaseHas('cutting_materials', [
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price1->id,
'combination_id' => $combination->id,
]);
$this->assertDatabaseHas('cutting_materials', [
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price2->id,
'combination_id' => $combination->id,
]);
});
test('store deducts stock for multiple materials', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price1 = RawMaterialPrice::factory()->create(['stock' => 100]);
$price2 = RawMaterialPrice::factory()->create(['stock' => 50]);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price1->id,
'material_usage' => 25,
'material_result' => 20,
'combination_index' => null,
],
[
'raw_material_price_id' => $price2->id,
'material_usage' => 15,
'material_result' => 12,
'combination_index' => 0,
],
],
'combinations' => [
['material_result' => 18],
],
]))->assertRedirect();
$price1->refresh();
$price2->refresh();
$this->assertEquals(75, $price1->stock);
$this->assertEquals(35, $price2->stock);
});
test('store with photo_key registers media', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'photo_key' => 'cuttings/test-photo.jpg',
]))->assertRedirect();
$cutting = Cutting::latest()->first();
$this->assertNotNull($cutting->getFirstMedia('photos'));
});
/*
|--------------------------------------------------------------------------
| STORE - STOCK VALIDATION
|--------------------------------------------------------------------------
*/
test('store fails when stock is insufficient', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 10]);
$response = $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 20,
'material_result' => 15,
'combination_index' => null,
],
],
]));
$response->assertRedirect();
$this->assertDatabaseCount('cuttings', 0);
$price->refresh();
$this->assertEquals(10, $price->stock);
});
/*
|--------------------------------------------------------------------------
| EDIT
|--------------------------------------------------------------------------
*/
test('user with permission can visit edit page', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$this->get(route('admin.manage.cuttings.edit', $cutting))->assertOk();
});
/*
|--------------------------------------------------------------------------
| UPDATE - VALIDATION
|--------------------------------------------------------------------------
*/
test('update requires product_name', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$payload = makeValidCuttingPayload();
unset($payload['product_name']);
$this->put(route('admin.manage.cuttings.update', $cutting), $payload)
->assertSessionHasErrors('product_name');
});
test('update requires sample', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$payload = makeValidCuttingPayload();
unset($payload['sample']);
$this->put(route('admin.manage.cuttings.update', $cutting), $payload)
->assertSessionHasErrors('sample');
});
test('update requires original_outside_sample', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$payload = makeValidCuttingPayload();
unset($payload['original_outside_sample']);
$this->put(route('admin.manage.cuttings.update', $cutting), $payload)
->assertSessionHasErrors('original_outside_sample');
});
test('update requires cutting_result', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$payload = makeValidCuttingPayload();
unset($payload['cutting_result']);
$this->put(route('admin.manage.cuttings.update', $cutting), $payload)
->assertSessionHasErrors('cutting_result');
});
/*
|--------------------------------------------------------------------------
| UPDATE - SUCCESS
|--------------------------------------------------------------------------
*/
test('user with permission can update cutting', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
'product_name' => 'Updated Product',
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 25,
'material_result' => 20,
'combination_index' => null,
],
],
]))->assertRedirect(route('admin.manage.cuttings.index'));
$this->assertDatabaseHas('cutting_results', [
'cutting_id' => $cutting->id,
'product_name' => 'Updated Product',
]);
});
test('update adjusts stock correctly', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
// First store with usage 20
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 20,
'material_result' => 15,
'combination_index' => null,
],
],
]));
$price->refresh();
$this->assertEquals(80, $price->stock);
$cutting = Cutting::latest()->first();
// Update: restore old (20), then deduct new (30) -> 80 + 20 - 30 = 70
$this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 30,
'material_result' => 25,
'combination_index' => null,
],
],
]));
$price->refresh();
$this->assertEquals(70, $price->stock);
});
test('update fails when stock is insufficient after restore', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 5]);
// Store with usage 3
$this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 3,
'material_result' => 2,
'combination_index' => null,
],
],
]));
$price->refresh();
$this->assertEquals(2, $price->stock);
$cutting = Cutting::latest()->first();
// Update: restore old (3) -> 2 + 3 = 5, then try to deduct 10 -> fail
// Stock remains at 5 because restore happens before validation
$this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
'materials' => [
[
'raw_material_price_id' => $price->id,
'material_usage' => 10,
'material_result' => 8,
'combination_index' => null,
],
],
]))->assertRedirect();
$price->refresh();
$this->assertEquals(5, $price->stock);
});
/*
|--------------------------------------------------------------------------
| DESTROY - SUCCESS
|--------------------------------------------------------------------------
*/
test('user with permission can delete cutting', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$this->delete(route('admin.manage.cuttings.destroy', $cutting))
->assertRedirect(route('admin.manage.cuttings.index'));
$this->assertSoftDeleted('cuttings', ['id' => $cutting->id]);
});
test('delete restores stock from raw_material_prices', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price = RawMaterialPrice::factory()->create(['stock' => 100]);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
CuttingMaterial::create([
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price->id,
'material_usage' => 30,
'material_result' => 25,
]);
$this->delete(route('admin.manage.cuttings.destroy', $cutting))
->assertRedirect(route('admin.manage.cuttings.index'));
$price->refresh();
$this->assertEquals(130, $price->stock);
});
test('delete restores stock for multiple materials', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$price1 = RawMaterialPrice::factory()->create(['stock' => 50]);
$price2 = RawMaterialPrice::factory()->create(['stock' => 30]);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
CuttingMaterial::create([
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price1->id,
'material_usage' => 20,
'material_result' => 15,
]);
CuttingMaterial::create([
'cutting_id' => $cutting->id,
'raw_material_price_id' => $price2->id,
'material_usage' => 10,
'material_result' => 8,
]);
$this->delete(route('admin.manage.cuttings.destroy', $cutting))
->assertRedirect(route('admin.manage.cuttings.index'));
$price1->refresh();
$price2->refresh();
$this->assertEquals(70, $price1->stock);
$this->assertEquals(40, $price2->stock);
});
test('delete removes cutting_material_combinations', function () {
$user = User::factory()->create();
giveCuttingPermissions($user);
$this->actingAs($user);
$cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
$combination = CuttingMaterialCombination::create([
'cutting_id' => $cutting->id,
'material_result' => 15,
]);
$this->delete(route('admin.manage.cuttings.destroy', $cutting))
->assertRedirect(route('admin.manage.cuttings.index'));
$this->assertSoftDeleted('cutting_material_combinations', ['id' => $combination->id]);
});