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

343 lines
12 KiB
PHP

<?php
use App\Enums\Permission as PermissionEnum;
use App\Enums\ProductStockQuality;
use App\Enums\StokOpnameStatus;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\StokOpname;
use App\Models\StokOpnameItem;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
});
// ─── Helper ───────────────────────────────────────────────
function createStokOpnameUserWithPermission(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 createCatalogSetup(): array
{
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create([
'product_id' => $product->id,
'stock' => 10,
'retail_stock' => 5,
'reject_stock' => 2,
]);
return ['product' => $product, 'variant' => $variant];
}
// ─── Index & Create ───────────────────────────────────────
describe('Stok Opname Index & Create', function () {
test('authenticated user with permission can view stok opnames index', function () {
$user = createStokOpnameUserWithPermission(PermissionEnum::STOK_OPNAMES_VIEW);
$this->actingAs($user)
->get(route('admin.manage.stok-opnames.index'))
->assertOk();
});
test('user without permission is forbidden to view index', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get(route('admin.manage.stok-opnames.index'))
->assertForbidden();
});
test('user with permission can view create page', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_CREATE
);
$this->actingAs($user)
->get(route('admin.manage.stok-opnames.create'))
->assertOk();
});
});
// ─── Store & Update ───────────────────────────────────────
describe('Stok Opname Store & Update', function () {
test('user can store a new stok opname', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_CREATE
);
$setup = createCatalogSetup();
$payload = [
'opname_date' => now()->format('Y-m-d'),
'notes' => 'Test Opname Notes',
'items' => [
[
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD->value,
'physical_stock' => 8,
'notes' => 'Kurang 2 bagus',
],
[
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::REJECT->value,
'physical_stock' => 3,
'notes' => 'Kelebihan 1 reject',
],
],
];
$this->actingAs($user)
->post(route('admin.manage.stok-opnames.store'), $payload)
->assertRedirect(route('admin.manage.stok-opnames.index'));
$this->assertDatabaseHas('stok_opnames', [
'notes' => 'Test Opname Notes',
'status' => StokOpnameStatus::DRAFT->value,
]);
$this->assertDatabaseHas('stok_opname_items', [
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD->value,
'physical_stock' => 8,
'difference' => -2,
]);
$this->assertDatabaseHas('stok_opname_items', [
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::REJECT->value,
'physical_stock' => 3,
'difference' => 1,
]);
});
test('user can auto-save a stok opname draft', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_CREATE
);
$setup = createCatalogSetup();
$payload = [
'opname_date' => now()->format('Y-m-d'),
'notes' => 'Draft Notes',
'items' => [
[
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::RETAIL->value,
'physical_stock' => 6,
'notes' => 'Tambah eceran',
],
],
];
$response = $this->actingAs($user)
->postJson(route('admin.manage.stok-opnames.auto_save'), $payload)
->assertOk();
$stokOpnameId = $response->json('stok_opname_id');
$this->assertDatabaseHas('stok_opnames', [
'id' => $stokOpnameId,
'status' => StokOpnameStatus::DRAFT->value,
]);
});
test('user can update a draft stok opname', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_UPDATE
);
$setup = createCatalogSetup();
$stokOpname = StokOpname::create([
'opname_date' => now(),
'status' => StokOpnameStatus::DRAFT,
'created_by_id' => $user->id,
]);
$item = StokOpnameItem::create([
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD,
'system_stock' => 10,
'physical_stock' => 8,
'difference' => -2,
]);
$payload = [
'opname_date' => now()->format('Y-m-d'),
'notes' => 'Updated Notes',
'items' => [
[
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD->value,
'physical_stock' => 9,
'notes' => 'Diubah jadi 9',
],
],
];
$this->actingAs($user)
->put(route('admin.manage.stok-opnames.update', $stokOpname), $payload)
->assertRedirect(route('admin.manage.stok-opnames.index'));
$this->assertDatabaseHas('stok_opnames', [
'id' => $stokOpname->id,
'notes' => 'Updated Notes',
]);
$this->assertDatabaseHas('stok_opname_items', [
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD->value,
'physical_stock' => 9,
'difference' => -1,
]);
});
});
// ─── Submit, Verify & Reject ──────────────────────────────
describe('Stok Opname Submission & Verification', function () {
test('user can submit a draft stok opname for verification', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_SUBMIT
);
$setup = createCatalogSetup();
$stokOpname = StokOpname::create([
'opname_date' => now(),
'status' => StokOpnameStatus::DRAFT,
'created_by_id' => $user->id,
]);
StokOpnameItem::create([
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD,
'system_stock' => 10,
'physical_stock' => 8,
'difference' => -2,
]);
$this->actingAs($user)
->post(route('admin.manage.stok-opnames.submit', $stokOpname))
->assertRedirect(route('admin.manage.stok-opnames.index'));
$this->assertEquals(StokOpnameStatus::PENDING, $stokOpname->refresh()->status);
});
test('user with verify permission can verify stok opname and reconcile multiple qualities', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_VERIFY
);
$setup = createCatalogSetup();
$stokOpname = StokOpname::create([
'opname_date' => now(),
'status' => StokOpnameStatus::PENDING,
'created_by_id' => $user->id,
]);
// Create opname items for Good, Retail, and Reject qualities
StokOpnameItem::create([
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::GOOD,
'system_stock' => 10,
'physical_stock' => 8,
'difference' => -2,
]);
StokOpnameItem::create([
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::RETAIL,
'system_stock' => 5,
'physical_stock' => 7,
'difference' => 2,
]);
StokOpnameItem::create([
'stok_opname_id' => $stokOpname->id,
'product_variant_id' => $setup['variant']->id,
'stock_quality' => ProductStockQuality::REJECT,
'system_stock' => 2,
'physical_stock' => 0,
'difference' => -2,
]);
$this->actingAs($user)
->post(route('admin.manage.stok-opnames.verify', $stokOpname), [
'verification_notes' => 'Verified successfully',
])
->assertRedirect(route('admin.manage.stok-opnames.index'));
$stokOpname->refresh();
$setup['variant']->refresh();
$this->assertEquals(StokOpnameStatus::VERIFIED, $stokOpname->status);
$this->assertEquals('Verified successfully', $stokOpname->verification_notes);
$this->assertEquals($user->id, $stokOpname->verified_by_id);
// Verify variant stocks are updated correctly matching their quality enums
$this->assertEquals(8, $setup['variant']->stock);
$this->assertEquals(7, $setup['variant']->retail_stock);
$this->assertEquals(0, $setup['variant']->reject_stock);
});
test('user with verify permission can reject stok opname', function () {
$user = createStokOpnameUserWithPermission(
PermissionEnum::STOK_OPNAMES_VIEW,
PermissionEnum::STOK_OPNAMES_VERIFY
);
$setup = createCatalogSetup();
$stokOpname = StokOpname::create([
'opname_date' => now(),
'status' => StokOpnameStatus::PENDING,
'created_by_id' => $user->id,
]);
$this->actingAs($user)
->post(route('admin.manage.stok-opnames.reject', $stokOpname), [
'reason' => 'Data is invalid',
])
->assertRedirect(route('admin.manage.stok-opnames.index'));
$stokOpname->refresh();
$this->assertEquals(StokOpnameStatus::REJECTED, $stokOpname->status);
$this->assertEquals('Data is invalid', $stokOpname->verification_notes);
});
});