store/tests/Feature/Admin/Manage/StockTest.php

317 lines
10 KiB
PHP

<?php
use App\Enums\CuttingStatus;
use App\Enums\Permission as PermissionEnum;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingResult;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
});
// ─── Helper ───────────────────────────────────────────────
function createStockUserWithPermission(PermissionEnum ...$permissions): User
{
$user = User::factory()->create();
$user->givePermissionTo(
array_merge(
[PermissionEnum::DASHBOARD_VIEW->value],
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
)
);
$user->forgetCachedPermissions();
return $user;
}
function createCompletedCuttingSetup(): array
{
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'reject_stock' => 5,
]);
$rawMaterial = RawMaterial::factory()->create();
$rawMaterialPrice = RawMaterialPrice::factory()->create([
'raw_material_id' => $rawMaterial->id,
]);
$cutting = Cutting::factory()->create([
'status' => CuttingStatus::COMPLETED,
]);
CuttingMaterial::factory()->create([
'cutting_id' => $cutting->id,
'raw_material_price_id' => $rawMaterialPrice->id,
]);
CuttingResult::factory()->create([
'cutting_id' => $cutting->id,
'product_variant_id' => $variant->id,
'cutting_result' => 7,
'sample' => 5, // Good stock result
'original_outside_sample' => 2, // Reject stock result
]);
return [
'cutting' => $cutting,
'variant' => $variant,
'raw_material_price' => $rawMaterialPrice,
];
}
// ─── Tests ────────────────────────────────────────────────
describe('Stock Warehouse Management', function () {
test('authenticated user with permission can view stocks index', function () {
$user = createStockUserWithPermission(PermissionEnum::STOCKS_VIEW);
$this->actingAs($user)
->get(route('admin.manage.stocks.index'))
->assertOk();
});
test('user with permission can submit verification for completed cutting', function () {
$user = createStockUserWithPermission(
PermissionEnum::STOCKS_VIEW,
PermissionEnum::CUTTINGS_VERIFY
);
$setup = createCompletedCuttingSetup();
$cutting = $setup['cutting'];
$payload = [
'verification_note' => 'Verification notes',
'results' => [
[
'product_variant_id' => $setup['variant']->id,
'good' => 6, // changed from 5 to 6
'reject' => 1, // changed from 2 to 1
],
],
'result_prices' => [
[
'product_variant_id' => $setup['variant']->id,
'prices' => [
[
'type' => 'harga_modal',
'price' => 50000,
],
[
'type' => 'retail',
'price' => 75000,
],
],
],
],
];
$this->actingAs($user)
->post(route('admin.manage.stocks.verify', $cutting), $payload)
->assertRedirect(route('admin.manage.stocks.index'));
$cutting->refresh();
$this->assertEquals(CuttingStatus::PENDING_VERIFICATION, $cutting->status);
// Verify cutting results are updated
$this->assertDatabaseHas('cutting_results', [
'cutting_id' => $cutting->id,
'product_variant_id' => $setup['variant']->id,
'sample' => 6,
'original_outside_sample' => 1,
]);
});
test('user with permission can approve verification and increase stock', function () {
$user = createStockUserWithPermission(
PermissionEnum::STOCKS_VIEW,
PermissionEnum::OWNER_VERIFICATIONS_VERIFY
);
$setup = createCompletedCuttingSetup();
$cutting = $setup['cutting'];
$cutting->status = CuttingStatus::PENDING_VERIFICATION;
$cutting->save();
$this->actingAs($user)
->post(route('admin.manage.owner_verifications.approve', $cutting), [
'approval_note' => 'Approved by owner',
])
->assertRedirect();
$cutting->refresh();
$setup['variant']->refresh();
$this->assertEquals(CuttingStatus::VERIFIED, $cutting->status);
// Stock (good) should increment by 5 (10 + 5 = 15)
$this->assertEquals(15, $setup['variant']->stock);
// Reject stock should increment by 2 (5 + 2 = 7)
$this->assertEquals(7, $setup['variant']->reject_stock);
});
test('user with permission can reject verification', function () {
$user = createStockUserWithPermission(
PermissionEnum::STOCKS_VIEW,
PermissionEnum::OWNER_VERIFICATIONS_REJECT
);
$setup = createCompletedCuttingSetup();
$cutting = $setup['cutting'];
$cutting->status = CuttingStatus::PENDING_VERIFICATION;
$cutting->save();
$this->actingAs($user)
->post(route('admin.manage.owner_verifications.reject', $cutting), [
'reason' => 'Invalid results',
])
->assertRedirect();
$cutting->refresh();
// Status should revert to COMPLETED
$this->assertEquals(CuttingStatus::COMPLETED, $cutting->status);
$this->assertDatabaseHas('rejections', [
'rejectable_id' => $cutting->id,
'rejectable_type' => Cutting::class,
'reason' => 'Invalid results',
]);
});
});
// ─── Retail Stock Transfer ──────────────────────────────────
describe('Retail Stock Transfer', function () {
test('user with permission can transfer stock from good to retail', function () {
$user = createStockUserWithPermission(PermissionEnum::STOCKS_VIEW);
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'retail_stock' => 5,
]);
$this->actingAs($user)
->postJson(route('admin.manage.retail-stock.transfer'), [
'product_variant_id' => $variant->id,
'quantity' => 3,
'notes' => 'Transfer test',
])
->assertOk();
$variant->refresh();
$this->assertEquals(7, $variant->stock);
$this->assertEquals(8, $variant->retail_stock);
$this->assertDatabaseHas('retail_stock_histories', [
'product_variant_id' => $variant->id,
'user_id' => $user->id,
'quantity' => 3,
'stock_before' => 10,
'retail_stock_before' => 5,
'stock_after' => 7,
'retail_stock_after' => 8,
'notes' => 'Transfer test',
]);
});
test('retail stock transfer does not require owner verification', function () {
$user = createStockUserWithPermission(PermissionEnum::STOCKS_VIEW);
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'retail_stock' => 5,
]);
$this->actingAs($user)
->postJson(route('admin.manage.retail-stock.transfer'), [
'product_variant_id' => $variant->id,
'quantity' => 3,
])
->assertOk();
$this->assertDatabaseCount('owner_verification_requests', 0);
$variant->refresh();
$this->assertEquals(7, $variant->stock);
$this->assertEquals(8, $variant->retail_stock);
});
test('retail stock transfer fails when quantity exceeds stock', function () {
$user = createStockUserWithPermission(PermissionEnum::STOCKS_VIEW);
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 5,
'retail_stock' => 0,
]);
$this->actingAs($user)
->postJson(route('admin.manage.retail-stock.transfer'), [
'product_variant_id' => $variant->id,
'quantity' => 10,
])
->assertUnprocessable()
->assertJsonValidationErrors('quantity');
});
test('retail stock transfer fails when quantity is zero', function () {
$user = createStockUserWithPermission(PermissionEnum::STOCKS_VIEW);
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'retail_stock' => 5,
]);
$this->actingAs($user)
->postJson(route('admin.manage.retail-stock.transfer'), [
'product_variant_id' => $variant->id,
'quantity' => 0,
])
->assertUnprocessable()
->assertJsonValidationErrors('quantity');
});
test('user without permission cannot transfer stock', function () {
$user = User::factory()->create();
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'retail_stock' => 5,
]);
$this->actingAs($user)
->postJson(route('admin.manage.retail-stock.transfer'), [
'product_variant_id' => $variant->id,
'quantity' => 3,
])
->assertForbidden();
});
});