343 lines
12 KiB
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);
|
|
});
|
|
});
|