212 lines
6.7 KiB
PHP
212 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Integration;
|
|
|
|
use Tests\TestCase;
|
|
use App\Modules\Core\Models\Tenant;
|
|
use App\Modules\Core\Models\User;
|
|
use App\Modules\Inventory\Models\Product;
|
|
use App\Modules\Inventory\Models\Warehouse;
|
|
use App\Modules\Inventory\Models\Inventory;
|
|
use App\Modules\Inventory\Models\Category;
|
|
use App\Modules\Inventory\Models\Unit;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
class InventoryQueryApiTest extends TestCase
|
|
{
|
|
protected $tenant;
|
|
protected $user;
|
|
protected $domain;
|
|
protected $warehouse;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
Artisan::call('migrate:fresh');
|
|
|
|
$this->domain = 'inventory-test-' . Str::random(8) . '.erp.local';
|
|
$tenantId = 'test_tenant_inv_' . Str::random(8);
|
|
|
|
tenancy()->central(function () use ($tenantId) {
|
|
$this->tenant = Tenant::create([
|
|
'id' => $tenantId,
|
|
'name' => 'Inventory Test Tenant',
|
|
]);
|
|
$this->tenant->domains()->create(['domain' => $this->domain]);
|
|
});
|
|
|
|
tenancy()->initialize($this->tenant);
|
|
Artisan::call('tenants:migrate');
|
|
|
|
$this->user = User::factory()->create(['name' => 'Inventory Admin']);
|
|
|
|
// 建立測試資料
|
|
$cat = Category::firstOrCreate(['name' => '飲品'], ['code' => 'CAT-DRINK']);
|
|
$unit = Unit::firstOrCreate(['name' => '瓶'], ['code' => 'BO']);
|
|
|
|
$p1 = Product::create([
|
|
'name' => '可口可樂',
|
|
'code' => 'COKE-001',
|
|
'barcode' => '4710001',
|
|
'external_pos_id' => 'POS-COKE',
|
|
'price' => 25,
|
|
'category_id' => $cat->id,
|
|
'base_unit_id' => $unit->id,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$p2 = Product::create([
|
|
'name' => '百事可樂',
|
|
'code' => 'PEPSI-001',
|
|
'barcode' => '4710002',
|
|
'external_pos_id' => 'POS-PEPSI',
|
|
'price' => 23,
|
|
'category_id' => $cat->id,
|
|
'base_unit_id' => $unit->id,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$this->warehouse = Warehouse::create([
|
|
'name' => '台北門市倉',
|
|
'code' => 'WH-TP-01',
|
|
'type' => 'retail',
|
|
]);
|
|
|
|
// 建立庫存
|
|
Inventory::create([
|
|
'warehouse_id' => $this->warehouse->id,
|
|
'product_id' => $p1->id,
|
|
'quantity' => 100,
|
|
'unit_cost' => 15,
|
|
'total_value' => 1500,
|
|
'batch_number' => 'BATCH-001',
|
|
'arrival_date' => now(),
|
|
]);
|
|
|
|
Inventory::create([
|
|
'warehouse_id' => $this->warehouse->id,
|
|
'product_id' => $p2->id,
|
|
'quantity' => 50,
|
|
'unit_cost' => 12,
|
|
'total_value' => 600,
|
|
'batch_number' => 'BATCH-002',
|
|
'arrival_date' => now(),
|
|
]);
|
|
|
|
tenancy()->end();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
if ($this->tenant) {
|
|
$this->tenant->delete();
|
|
}
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_can_query_all_inventory_for_warehouse()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonPath('status', 'success')
|
|
->assertJsonCount(2, 'data');
|
|
}
|
|
|
|
public function test_can_filter_inventory_by_product_id()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
// 先找出可樂的 ERP ID
|
|
$productId = Product::where('code', 'COKE-001')->value('id');
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}?product_id={$productId}");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonCount(1, 'data')
|
|
->assertJsonPath('data.0.product_code', 'COKE-001')
|
|
->assertJsonPath('data.0.quantity', 100);
|
|
}
|
|
|
|
public function test_can_filter_inventory_by_external_pos_id()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}?external_pos_id=POS-COKE");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonCount(1, 'data')
|
|
->assertJsonPath('data.0.external_pos_id', 'POS-COKE')
|
|
->assertJsonPath('data.0.quantity', 100);
|
|
}
|
|
|
|
public function test_can_filter_inventory_by_barcode()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}?barcode=4710002");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonCount(1, 'data')
|
|
->assertJsonPath('data.0.product_code', 'PEPSI-001')
|
|
->assertJsonPath('data.0.quantity', 50);
|
|
}
|
|
|
|
public function test_can_filter_inventory_by_code()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}?code=COKE-001");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonCount(1, 'data')
|
|
->assertJsonPath('data.0.product_code', 'COKE-001');
|
|
}
|
|
|
|
public function test_returns_empty_when_no_match()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/{$this->warehouse->code}?product_id=NON-EXISTENT");
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonCount(0, 'data');
|
|
}
|
|
|
|
public function test_returns_404_when_warehouse_not_found()
|
|
{
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
|
|
$response = $this->withHeaders([
|
|
'X-Tenant-Domain' => $this->domain,
|
|
'Accept' => 'application/json',
|
|
])->getJson("/api/v1/integration/inventory/INVALID-WH");
|
|
|
|
$response->assertStatus(404);
|
|
}
|
|
}
|