feat(integration): 實作並測試 POS 與販賣機訂單同步 API
主要變更: - 實作 POS 與販賣機訂單同步邏輯,支援多租戶與 Sanctum 驗證。 - 修正多租戶識別中間件與 Sanctum 驗證順序問題。 - 切換快取驅動至 Redis 以支援 Tenancy 標籤功能。 - 新增商品同步 API (Upsert) 及相關單元測試。 - 新增手動測試腳本 tests/manual/test_integration_api.sh。 - 前端新增銷售訂單來源篩選與欄位顯示。
This commit is contained in:
151
tests/Feature/Integration/ProductSyncTest.php
Normal file
151
tests/Feature/Integration/ProductSyncTest.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?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\Category;
|
||||
use Stancl\Tenancy\Facades\Tenancy;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProductSyncTest extends TestCase
|
||||
{
|
||||
protected $tenant;
|
||||
protected $user;
|
||||
protected $domain;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// 每次測試前重置資料庫(在測試環境)
|
||||
\Artisan::call('migrate:fresh');
|
||||
|
||||
$this->domain = 'product-test-' . Str::random(8) . '.erp.local';
|
||||
$tenantId = 'test_tenant_p_' . Str::random(8);
|
||||
|
||||
// 建立租戶
|
||||
tenancy()->central(function () use ($tenantId) {
|
||||
$this->tenant = Tenant::create([
|
||||
'id' => $tenantId,
|
||||
'name' => 'Product Test Tenant',
|
||||
]);
|
||||
$this->tenant->domains()->create(['domain' => $this->domain]);
|
||||
});
|
||||
|
||||
// 初始化租戶並遷移
|
||||
tenancy()->initialize($this->tenant);
|
||||
\Artisan::call('tenants:migrate');
|
||||
|
||||
// 建立測試使用者與分類
|
||||
$this->user = User::factory()->create(['name' => 'Test Admin']);
|
||||
Category::create(['name' => '測試分類', 'code' => 'TEST-CAT']);
|
||||
|
||||
tenancy()->end();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if ($this->tenant) {
|
||||
$this->tenant->delete();
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* 測試產品同步新增功能
|
||||
*/
|
||||
public function test_product_sync_can_create_new_product()
|
||||
{
|
||||
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
|
||||
|
||||
$payload = [
|
||||
'external_pos_id' => 'POS-NEW-999',
|
||||
'name' => '全新同步商品',
|
||||
'price' => 299,
|
||||
'barcode' => '1234567890123',
|
||||
'category' => '測試分類',
|
||||
'cost_price' => 150
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-Tenant-Domain' => $this->domain,
|
||||
'Accept' => 'application/json',
|
||||
])->postJson('/api/v1/integration/products/upsert', $payload);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonPath('message', 'Product synced successfully');
|
||||
|
||||
// 驗證租戶資料庫
|
||||
tenancy()->initialize($this->tenant);
|
||||
$this->assertDatabaseHas('products', [
|
||||
'external_pos_id' => 'POS-NEW-999',
|
||||
'name' => '全新同步商品',
|
||||
'price' => 299,
|
||||
]);
|
||||
tenancy()->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 測試產品同步更新功能 (Upsert)
|
||||
*/
|
||||
public function test_product_sync_can_update_existing_product()
|
||||
{
|
||||
// 先建立一個既有商品
|
||||
tenancy()->initialize($this->tenant);
|
||||
Product::create([
|
||||
'name' => '舊商品名稱',
|
||||
'code' => 'OLD-CODE',
|
||||
'external_pos_id' => 'POS-EXIST-001',
|
||||
'price' => 100,
|
||||
'category_id' => Category::first()->id,
|
||||
]);
|
||||
tenancy()->end();
|
||||
|
||||
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
|
||||
|
||||
$payload = [
|
||||
'external_pos_id' => 'POS-EXIST-001',
|
||||
'name' => '更新後的商品名稱',
|
||||
'price' => 888,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-Tenant-Domain' => $this->domain,
|
||||
'Accept' => 'application/json',
|
||||
])->postJson('/api/v1/integration/products/upsert', $payload);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
tenancy()->initialize($this->tenant);
|
||||
$this->assertDatabaseHas('products', [
|
||||
'external_pos_id' => 'POS-EXIST-001',
|
||||
'name' => '更新後的商品名稱',
|
||||
'price' => 888,
|
||||
]);
|
||||
tenancy()->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 測試產品同步驗證失敗
|
||||
*/
|
||||
public function test_product_sync_validation_fails_without_required_fields()
|
||||
{
|
||||
\Laravel\Sanctum\Sanctum::actingAs($this->user, ['*']);
|
||||
|
||||
$payload = [
|
||||
'external_pos_id' => '', // 缺少此欄位
|
||||
'name' => '', // 缺少此欄位
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-Tenant-Domain' => $this->domain,
|
||||
'Accept' => 'application/json',
|
||||
])->postJson('/api/v1/integration/products/upsert', $payload);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonValidationErrors(['external_pos_id', 'name']);
|
||||
}
|
||||
}
|
||||
83
tests/manual/test_integration_api.sh
Executable file
83
tests/manual/test_integration_api.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Star ERP 整合 API 手動測試腳本
|
||||
# 用途:手動驗證商品同步、POS 訂單、販賣機訂單 API
|
||||
|
||||
# --- 設定區 ---
|
||||
BASE_URL=${1:-"http://localhost:8081"}
|
||||
TOKEN=${2:-"YOUR_TOKEN_HERE"}
|
||||
TENANT_DOMAIN="localhost"
|
||||
|
||||
echo "=== Star ERP Integration API Test ==="
|
||||
echo "Target URL: $BASE_URL"
|
||||
echo "Tenant Domain: $TENANT_DOMAIN"
|
||||
echo ""
|
||||
|
||||
# 顏色定義
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 輔助函式:執行 curl
|
||||
function call_api() {
|
||||
local method=$1
|
||||
local path=$2
|
||||
local data=$3
|
||||
local title=$4
|
||||
|
||||
echo -e "Testing: ${GREEN}$title${NC} ($path)"
|
||||
|
||||
curl -s -X "$method" "$BASE_URL$path" \
|
||||
-H "X-Tenant-Domain: $TENANT_DOMAIN" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" | jq .
|
||||
|
||||
echo -e "-----------------------------------\n"
|
||||
}
|
||||
|
||||
# 1. 商品同步 (Upsert)
|
||||
PRODUCT_ID="TEST-AUTO-$(date +%s)"
|
||||
call_api "POST" "/api/v1/integration/products/upsert" "{
|
||||
\"external_pos_id\": \"$PRODUCT_ID\",
|
||||
\"name\": \"自動測試商品 $(date +%H%M%S)\",
|
||||
\"price\": 150,
|
||||
\"barcode\": \"690123456789\",
|
||||
\"category\": \"飲品\",
|
||||
\"cost_price\": 80
|
||||
}" "Product Upsert"
|
||||
|
||||
# 2. POS 訂單同步
|
||||
call_api "POST" "/api/v1/integration/orders" "{
|
||||
\"external_order_id\": \"POS-AUTO-$(date +%s)\",
|
||||
\"status\": \"completed\",
|
||||
\"payment_method\": \"cash\",
|
||||
\"sold_at\": \"$(date -Iseconds)\",
|
||||
\"warehouse_id\": 2,
|
||||
\"items\": [
|
||||
{
|
||||
\"pos_product_id\": \"TEST-FINAL-VERIFY\",
|
||||
\"qty\": 1,
|
||||
\"price\": 100
|
||||
}
|
||||
]
|
||||
}" "POS Order Sync"
|
||||
|
||||
# 3. 販賣機訂單同步
|
||||
call_api "POST" "/api/v1/integration/vending/orders" "{
|
||||
\"external_order_id\": \"VEND-AUTO-$(date +%s)\",
|
||||
\"machine_id\": \"Vending-Machine-Test-01\",
|
||||
\"payment_method\": \"line_pay\",
|
||||
\"sold_at\": \"$(date -Iseconds)\",
|
||||
\"warehouse_id\": 2,
|
||||
\"items\": [
|
||||
{
|
||||
\"product_code\": \"FINAL-OK\",
|
||||
\"qty\": 2,
|
||||
\"price\": 50
|
||||
}
|
||||
]
|
||||
}" "Vending Machine Order Sync"
|
||||
|
||||
echo "Test Completed."
|
||||
Reference in New Issue
Block a user