[REFACTOR] 統一訂單同步 API 錯誤回應與修正 Linter 警告

This commit is contained in:
2026-03-19 14:07:32 +08:00
parent e3ceedc579
commit 0b4aeacb55
15 changed files with 1173 additions and 108 deletions

View File

@@ -0,0 +1,211 @@
<?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);
}
}