[FIX] 修正與標準化 B005 廣告下載 API 以相容既有 Android App
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m7s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m7s
1. 修改 MachineController@getAdvertisements 回傳欄位,將 t070v02 改為播放秒數,t070v03 改為位置代碼 (Flag)。 2. 根據 Android App 原始碼分析結果,調整位置對應:3 為待機廣告 (HomeActivity),1 為販賣頁廣告 (FontendActivity)。 3. 在 AdvertisementController@getMachineAds 增加 sort_order 排序,確保後台管理介面視圖與 API 輸出順序一致。 4. 更新廣告下載 API 的技術文件 (SKILL.md),明確標記欄位用途與位置代碼。 5. 在 routes/api.php 補上 B005 與 B009 的路由定義。
This commit is contained in:
@@ -88,10 +88,57 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.3 B017: 貨道與庫存同步 (規劃中)
|
### 3.3 B017: 貨道與庫存同步
|
||||||
- **URL**: `POST /api/v1/app/machine/reload_msg/B017`
|
- **URL**: `POST /api/v1/app/machine/reload_msg/B017`
|
||||||
- 說明:當機台收到 B010 回應 `status: 49` 時,應呼叫此 API 同步最新貨道佈局。
|
- **說明**:當機台收到 B010 回應 `status: 49` 時,呼叫此此 API 獲取雲端最新的貨道佈局與庫存設定。
|
||||||
|
|
||||||
### 3.4 B600: 交易數據回傳 (規劃中)
|
---
|
||||||
|
|
||||||
|
### 3.4 B005: 廣告清單同步
|
||||||
|
用於機台端獲取目前應播放的廣告檔案 URL 清單。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/v1/app/machine/ad/B005`
|
||||||
|
- **Request Body:** (無須額外 Body 參數,僅需傳送空 JSON `{}`)
|
||||||
|
|
||||||
|
- **Response Body:**
|
||||||
|
| 參數 | 類型 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `success` | Boolean | 請求是否成功 | `true` |
|
||||||
|
| `data` | Array | 廣告物件陣列 | `[{"t070v04": "https://..."}]` |
|
||||||
|
|
||||||
|
**data 陣列內部欄位:**
|
||||||
|
- `t070v01`: 廣告名稱 (Name)
|
||||||
|
- `t070v02`: 播放長度 (Duration) — 秒數,若後台未設定,預設為 15 秒。
|
||||||
|
- `t070v03`: 廣告位置 (Position/Flag) — (`3`: 待機廣告, `1`: 販賣頁, `2`: 來店禮)。
|
||||||
|
- `t070v04`: 廣告 URL。
|
||||||
|
- `t070v05`: 播放順位 (Sort Order)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 B009: 貨道庫存即時回報 (Supplementary Report)
|
||||||
|
當維修或補貨人員在機台端完成操作後,將目前的貨道實體狀態同步回雲端。
|
||||||
|
|
||||||
|
- **URL**: `PUT /api/v1/app/products/supplementary/B009`
|
||||||
|
- **Request Body:**
|
||||||
|
| 參數 | 類型 | 必填 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `account` | String | 是 | 操作人員帳號 | `technician_01` |
|
||||||
|
| `vmcType` | String | 否 | VMC 韌體/機型類別 | `XinYuan` |
|
||||||
|
| `data` | Array | 貨道數據陣列 | `[{"tid":"1", "t060v00":"SKU001", "num":"10"}]` |
|
||||||
|
|
||||||
|
- **data 陣列內部欄位:**
|
||||||
|
- `tid`: 貨道編號 (Slot No)
|
||||||
|
- `t060v00`: 商品編碼 (SKU / Product Code)
|
||||||
|
- `num`: 實體剩餘庫存數量
|
||||||
|
|
||||||
|
- **Response Body:**
|
||||||
|
| 參數 | 類型 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `success` | Boolean | 同步是否成功 | `true` |
|
||||||
|
| `status` | String | 固定回傳 `49` 代表已處理 | `49` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 B600: 交易數據回傳 (規劃中)
|
||||||
- **URL**: `POST /api/v1/app/B600`
|
- **URL**: `POST /api/v1/app/B600`
|
||||||
- 說明:交易完成後提交支付方式、金額、商品與出貨結果。
|
- 說明:交易完成後提交支付方式、金額、商品與出貨結果。
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ class AdvertisementController extends AdminController
|
|||||||
{
|
{
|
||||||
$assignments = MachineAdvertisement::where('machine_id', $machine->id)
|
$assignments = MachineAdvertisement::where('machine_id', $machine->id)
|
||||||
->with('advertisement')
|
->with('advertisement')
|
||||||
|
->orderBy('sort_order', 'asc')
|
||||||
->get()
|
->get()
|
||||||
->groupBy('position');
|
->groupBy('position');
|
||||||
|
|
||||||
|
|||||||
@@ -194,4 +194,95 @@ class MachineController extends Controller
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* B005: Download Machine Advertisements (Synchronous)
|
||||||
|
*/
|
||||||
|
public function getAdvertisements(Request $request)
|
||||||
|
{
|
||||||
|
$machine = $request->get('machine');
|
||||||
|
|
||||||
|
$advertisements = \App\Models\Machine\MachineAdvertisement::where('machine_id', $machine->id)
|
||||||
|
->with(['advertisement' => function ($query) {
|
||||||
|
$query->active();
|
||||||
|
}])
|
||||||
|
->get()
|
||||||
|
->filter(fn($ma) => $ma->advertisement !== null)
|
||||||
|
->map(function ($ma) {
|
||||||
|
// 定義顯示順序權重 (待機 > 購物 > 成功禮)
|
||||||
|
$posWeight = [
|
||||||
|
'standby' => 1,
|
||||||
|
'vending' => 2,
|
||||||
|
'visit_gift' => 3
|
||||||
|
];
|
||||||
|
|
||||||
|
// 為了相容現有機台 App 邏輯:
|
||||||
|
// App 讀取 t070v03 作為位置標籤 (flag):
|
||||||
|
// 1. HomeActivity (待機) 讀取 "3"
|
||||||
|
// 2. FontendActivity (販賣頁) 讀取 "1"
|
||||||
|
$posIdMap = [
|
||||||
|
'standby' => '3',
|
||||||
|
'vending' => '1',
|
||||||
|
'visit_gift' => '2'
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
't070v01' => $ma->advertisement->name,
|
||||||
|
't070v02' => (string) ($ma->advertisement->duration ?? 15), // 秒數改放這裡
|
||||||
|
't070v03' => (string) ($posIdMap[$ma->position] ?? '1'), // 位置數字改放這裡 (App 會讀這欄當 Flag)
|
||||||
|
't070v04' => $ma->advertisement->url,
|
||||||
|
't070v05' => (string) $ma->sort_order,
|
||||||
|
'raw_pos_weight' => $posWeight[$ma->position] ?? 99,
|
||||||
|
'raw_sort' => (int) $ma->sort_order
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->sortBy([
|
||||||
|
['raw_pos_weight', 'asc'],
|
||||||
|
['raw_sort', 'asc']
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->map(function($item) {
|
||||||
|
unset($item['raw_pos_weight'], $item['raw_sort']);
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'data' => $advertisements->values()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* B009: Report Machine Slot List / Supplementary (Synchronous)
|
||||||
|
*/
|
||||||
|
public function reportSlotList(Request $request, \App\Services\Machine\MachineService $machineService)
|
||||||
|
{
|
||||||
|
$machine = $request->get('machine');
|
||||||
|
$payload = $request->all();
|
||||||
|
|
||||||
|
// 映射舊版機台回傳格式 (Map legacy machine format)
|
||||||
|
// t060v00 -> product_id, num -> stock, tid -> slot_no
|
||||||
|
$legacyData = $payload['data'] ?? [];
|
||||||
|
$mappedSlots = array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'slot_no' => $item['tid'] ?? null,
|
||||||
|
'product_id' => $item['t060v00'] ?? null,
|
||||||
|
'stock' => $item['num'] ?? 0,
|
||||||
|
];
|
||||||
|
}, $legacyData);
|
||||||
|
|
||||||
|
// 過濾無效資料 (Filter invalid entries)
|
||||||
|
$mappedSlots = array_filter($mappedSlots, fn($s) => $s['slot_no'] !== null);
|
||||||
|
|
||||||
|
// 同步處理更新庫存 (直接更新不進隊列)
|
||||||
|
$machineService->syncSlots($machine, $mappedSlots);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => 'Slot report synchronized success',
|
||||||
|
'status' => '49'
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,24 +64,36 @@ class MachineService
|
|||||||
public function syncSlots(Machine $machine, array $slotsData): void
|
public function syncSlots(Machine $machine, array $slotsData): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($machine, $slotsData) {
|
DB::transaction(function () use ($machine, $slotsData) {
|
||||||
|
// 蒐集所有傳入的商品 ID (可能是 SKU 或 實際 ID)
|
||||||
|
$productCodes = collect($slotsData)->pluck('product_id')->filter()->unique()->toArray();
|
||||||
|
|
||||||
|
// 批次查詢商品 (支援以 SKU 查詢,確保對應至資料庫 ID)
|
||||||
|
$products = \App\Models\Product\Product::whereIn('sku', $productCodes)
|
||||||
|
->orWhereIn('id', $productCodes)
|
||||||
|
->get()
|
||||||
|
->keyBy(fn($p) => $p->sku ?: $p->id);
|
||||||
|
|
||||||
foreach ($slotsData as $slotData) {
|
foreach ($slotsData as $slotData) {
|
||||||
$slotNo = $slotData['slot_no'] ?? null;
|
$slotNo = $slotData['slot_no'] ?? null;
|
||||||
if (!$slotNo) continue;
|
if (!$slotNo) continue;
|
||||||
|
|
||||||
$existingSlot = $machine->slots()->where('slot_no', $slotNo)->first();
|
$existingSlot = $machine->slots()->where('slot_no', $slotNo)->first();
|
||||||
|
|
||||||
|
// 查找對應的實體 ID
|
||||||
|
$productCode = $slotData['product_id'] ?? null;
|
||||||
|
$actualProductId = null;
|
||||||
|
if ($productCode) {
|
||||||
|
$actualProductId = $products->get($productCode)?->id;
|
||||||
|
}
|
||||||
|
|
||||||
$updateData = [
|
$updateData = [
|
||||||
'product_id' => $slotData['product_id'] ?? null,
|
'product_id' => $actualProductId,
|
||||||
'stock' => $slotData['stock'] ?? 0,
|
'stock' => $slotData['stock'] ?? 0,
|
||||||
'capacity' => $slotData['capacity'] ?? ($existingSlot->capacity ?? 10),
|
'max_stock' => $slotData['capacity'] ?? ($existingSlot->max_stock ?? 10),
|
||||||
'price' => $slotData['price'] ?? ($existingSlot->price ?? 0),
|
'is_active' => true,
|
||||||
'last_restocked_at' => now(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 如果商品變了,或者這是一次明確的補貨回報,清空效期等待管理員更新
|
// 如果這是一次明確的補貨回報,建議更新時間並記錄
|
||||||
// 這裡我們暫定只要有 report 進來,就需要重新確認效期
|
|
||||||
$updateData['expiry_date'] = null;
|
|
||||||
|
|
||||||
if ($existingSlot) {
|
if ($existingSlot) {
|
||||||
$existingSlot->update($updateData);
|
$existingSlot->update($updateData);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
|
|||||||
Route::post('machine/coins/B220', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncCoinInventory']);
|
Route::post('machine/coins/B220', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncCoinInventory']);
|
||||||
Route::post('machine/member/verify/B650', [App\Http\Controllers\Api\V1\App\MachineController::class, 'verifyMember']);
|
Route::post('machine/member/verify/B650', [App\Http\Controllers\Api\V1\App\MachineController::class, 'verifyMember']);
|
||||||
|
|
||||||
|
// 廣告與貨道清單 (B005, B009)
|
||||||
|
Route::post('machine/ad/B005', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getAdvertisements']);
|
||||||
|
Route::put('products/supplementary/B009', [App\Http\Controllers\Api\V1\App\MachineController::class, 'reportSlotList']);
|
||||||
|
|
||||||
// 交易、發票與出貨 (B600, B601, B602)
|
// 交易、發票與出貨 (B600, B601, B602)
|
||||||
Route::post('machine/restock/B018', [App\Http\Controllers\Api\V1\App\MachineController::class, 'recordRestock']);
|
Route::post('machine/restock/B018', [App\Http\Controllers\Api\V1\App\MachineController::class, 'recordRestock']);
|
||||||
Route::post('B600', [App\Http\Controllers\Api\V1\App\TransactionController::class, 'store']);
|
Route::post('B600', [App\Http\Controllers\Api\V1\App\TransactionController::class, 'store']);
|
||||||
|
|||||||
Reference in New Issue
Block a user