[REFACTOR] 溫度回報邏輯優化與 MQTT 規格標準化

1. 優化 ProcessHeartbeat 溫度處理邏輯,改為「變動即記錄」並標準化為整數型態。
2. 在 Machine Model 中新增 temperature 欄位的 integer cast。
3. 更新 MQTT 與 API 技術規格技能文件 (SKILL.md),同步最新的溫度記錄規則。
4. 更新 config/api-docs.php,明確標註心跳上報之溫度規格與日誌規則。
5. 移除 routes/api.php 中已廢棄之 B600 與機台日誌 REST API 路由。
6. 清理 docs 目錄,將初期計畫文件遷移至 docs/plan/。
This commit is contained in:
sky121113 2026-04-22 17:02:24 +08:00
parent 12591a55f2
commit 40aec884b0
27 changed files with 40 additions and 785 deletions

View File

@ -153,6 +153,7 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
### 3.12 B010: [DEPRECATED / REMOVED] 心跳上報與狀態同步 ### 3.12 B010: [DEPRECATED / REMOVED] 心跳上報與狀態同步
> [!WARNING] > [!WARNING]
> **此 REST API 已廢棄並移除**。機台心跳上報與指令下發已全面遷移至 **MQTT** 架構。 > **此 REST API 已廢棄並移除**。機台心跳上報與指令下發已全面遷移至 **MQTT** 架構。
> (註:溫度數據已標準化為 **Integer**,且雲端採「變動即記錄」規則)。
> 詳細格式請參閱:[MQTT 通訊規範 - B010 心跳上報](file:///home/mama/projects/star-cloud/.agents/skills/mqtt-communication-specs/SKILL.md#31-心跳上報-b010) > 詳細格式請參閱:[MQTT 通訊規範 - B010 心跳上報](file:///home/mama/projects/star-cloud/.agents/skills/mqtt-communication-specs/SKILL.md#31-心跳上報-b010)
--- ---

View File

@ -123,7 +123,8 @@ Mqtt3Client client = MqttClient.builder()
``` ```
> [!NOTE] > [!NOTE]
> 為了降低傳輸開銷,目前僅要求上報 **韌體版本****溫度**。其餘狀態如連線/斷線已透過 LWT 與 $SYS 事件處理。 > 為了降低傳輸開銷,目前僅要求上報 **韌體版本****溫度** (整數型態)。其餘狀態如連線/斷線已透過 LWT 與 $SYS 事件處理。
> 雲端對於溫度的日誌記錄規則:**只要有變動 (current !== last) 即寫入日誌**,並維持每 4 小時一次的保底記錄。
### 3.2 異常與錯誤上報 (B013) ### 3.2 異常與錯誤上報 (B013)
當機台發生硬體故障(如卡貨、通訊中斷)時,發布至此 Topic。 當機台發生硬體故障(如卡貨、通訊中斷)時,發布至此 Topic。
@ -158,10 +159,19 @@ Mqtt3Client client = MqttClient.builder()
```json ```json
{ {
"action": "create", "action": "create",
"payment_type": "credit_card", "flow_id": "T202604140001",
"amount": 25.0, "payment_type": 1,
"slot_no": 3, "total_amount": 25.0,
"product_id": "5" "discount_amount": 0.0,
"pay_amount": 25.0,
"items": [
{
"product_id": "5",
"product_name": "Coca Cola",
"price": 25.0,
"quantity": 1
}
]
} }
``` ```
@ -169,11 +179,12 @@ Mqtt3Client client = MqttClient.builder()
```json ```json
{ {
"action": "invoice", "action": "invoice",
"flow_id": "T202604140001",
"invoice_no": "AB12345678", "invoice_no": "AB12345678",
"transaction_ref": "T202604140001",
"invoice_date": "2026-04-22", "invoice_date": "2026-04-22",
"buyer_tax_id": "", "amount": 25.0,
"amount": 25.0 "random_no": "1234",
"love_code": ""
} }
``` ```
@ -181,10 +192,13 @@ Mqtt3Client client = MqttClient.builder()
```json ```json
{ {
"action": "dispense", "action": "dispense",
"slot_no": 3, "flow_id": "T202604140001",
"result": "success", "slot_no": "3",
"transaction_ref": "T202604140001", "product_id": "5",
"remaining_stock": 9 "amount": 25.0,
"dispense_status": 1,
"remaining_stock": 9,
"machine_time": "2026-04-22 14:30:00"
} }
``` ```

View File

@ -1,66 +0,0 @@
<?php
namespace App\Http\Controllers\Api\V1\App;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Jobs\Transaction\ProcessTransaction;
use App\Jobs\Transaction\ProcessInvoice;
use App\Jobs\Transaction\ProcessDispenseRecord;
class TransactionController extends Controller
{
/**
* B600: Record Transaction (Asynchronous)
*/
public function store(Request $request)
{
$machine = $request->get('machine');
$data = $request->except(['machine', 'key']); // 排除 Middleware 注入物件
$data['serial_no'] = $machine->serial_no;
ProcessTransaction::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
/**
* B601: Record Invoice (Asynchronous)
*/
public function recordInvoice(Request $request)
{
$machine = $request->get('machine');
$data = $request->except(['machine', 'key']); // 排除 Middleware 注入物件
$data['serial_no'] = $machine->serial_no;
ProcessInvoice::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
/**
* B602: Record Dispense Result (Asynchronous)
*/
public function recordDispense(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
$data['serial_no'] = $machine->serial_no;
ProcessDispenseRecord::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Jobs\Machine\ProcessMachineLog;
use App\Models\Machine\Machine;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class MachineController extends ApiController
{
/**
* 接收機台回傳的日誌 (IoT Endpoint)
* 採用異步處理 (Queue)
*/
public function storeLog(Request $request, int $id): JsonResponse
{
$validator = Validator::make($request->all(), [
'level' => 'required|string|in:info,warning,error',
'message' => 'required|string',
'context' => 'nullable|array',
]);
if ($validator->fails()) {
return $this->errorResponse('Validation error', 422, $validator->errors());
}
// 檢查機台是否存在
if (!Machine::where('id', $id)->exists()) {
return $this->errorResponse('Machine not found', 404);
}
// 丟入隊列進行異步處理,回傳 202 Accepted
ProcessMachineLog::dispatch($id, $request->only(['level', 'message', 'context']));
return $this->successResponse([], 'Log accepted. Processing asynchronously.', 202);
}
}

View File

@ -66,9 +66,10 @@ class ProcessHeartbeat implements ShouldQueue
$newState['firmware_version'] = $fv; $newState['firmware_version'] = $fv;
} }
// 2. 溫度更新 (智慧過濾日誌) // 2. 溫度更新 (只要有變動就紀錄日誌)
if (isset($this->payload['temperature'])) { if (isset($this->payload['temperature'])) {
$temp = $this->payload['temperature']; $temp = (int) $this->payload['temperature'];
Log::debug("ProcessHeartbeat: Temperature reported for {$this->serialNo}: {$temp}");
$updateData['temperature'] = $temp; $updateData['temperature'] = $temp;
// 讀取最後一次記錄日誌的溫度與時間 // 讀取最後一次記錄日誌的溫度與時間
@ -79,14 +80,15 @@ class ProcessHeartbeat implements ShouldQueue
if (!$lastLogEntry) { if (!$lastLogEntry) {
$shouldLog = true; $shouldLog = true;
} else { } else {
$lastValue = (float) $lastLogEntry['value']; $lastValue = (int) $lastLogEntry['value'];
$lastAt = \Carbon\Carbon::parse($lastLogEntry['at']); $lastAt = \Carbon\Carbon::parse($lastLogEntry['at']);
// 條件 A: 變動超過 3°C // 條件 A: 只要溫度與上次記錄不同就記錄 (整數比對)
if (abs($temp - $lastValue) >= 3.0) { if ($temp !== $lastValue) {
Log::debug("ProcessHeartbeat: Temperature changed from {$lastValue} to {$temp}. Triggering log.");
$shouldLog = true; $shouldLog = true;
} }
// 條件 B: 距離上次日誌超過 4 小時 // 條件 B: 距離上次日誌超過 4 小時 (保底機制)
if ($lastAt->diffInHours(now()) >= 4) { if ($lastAt->diffInHours(now()) >= 4) {
$shouldLog = true; $shouldLog = true;
} }

View File

@ -1,46 +0,0 @@
<?php
namespace App\Jobs\Machine;
use App\Services\Machine\MachineService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessMachineLog implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var int
*/
protected $machineId;
/**
* @var array
*/
protected $logData;
public function __construct(int $machineId, array $logData)
{
$this->machineId = $machineId;
$this->logData = $logData;
}
public function getMachineId(): int
{
return $this->machineId;
}
public function handle(MachineService $service): void
{
try {
$service->recordLog($this->machineId, $this->logData);
} catch (\Exception $e) {
Log::error("Failed to process machine log for machine {$this->machineId}: " . $e->getMessage());
}
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Jobs\Transaction;
use App\Services\Transaction\TransactionService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessTransaction implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $data;
/**
* Create a new job instance.
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* Execute the job.
*/
public function handle(TransactionService $transactionService): void
{
try {
$transactionService->processTransaction($this->data);
} catch (\Exception $e) {
Log::error("Failed to process transaction: " . $e->getMessage());
throw $e;
}
}
}

View File

@ -157,6 +157,7 @@ class Machine extends Model
protected $casts = [ protected $casts = [
'last_heartbeat_at' => 'datetime', 'last_heartbeat_at' => 'datetime',
'temperature' => 'integer',
'welcome_gift_enabled' => 'boolean', 'welcome_gift_enabled' => 'boolean',
'is_spring_slot_1_10' => 'boolean', 'is_spring_slot_1_10' => 'boolean',
'is_spring_slot_11_20' => 'boolean', 'is_spring_slot_11_20' => 'boolean',

View File

@ -112,6 +112,7 @@ class TransactionService
'slot_no' => $data['slot_no'] ?? 'unknown', 'slot_no' => $data['slot_no'] ?? 'unknown',
'product_id' => $data['product_id'] ?? null, 'product_id' => $data['product_id'] ?? null,
'amount' => $data['amount'] ?? 0, 'amount' => $data['amount'] ?? 0,
'remaining_stock' => $data['remaining_stock'] ?? null,
'dispense_status' => $data['dispense_status'] ?? 0, 'dispense_status' => $data['dispense_status'] ?? 0,
'machine_time' => $data['machine_time'] ?? now(), 'machine_time' => $data['machine_time'] ?? now(),
]); ]);

View File

@ -427,13 +427,13 @@ return [
'action' => 'PUB', 'action' => 'PUB',
'topic' => 'machine/{serial_no}/heartbeat', 'topic' => 'machine/{serial_no}/heartbeat',
'qos' => 0, 'qos' => 0,
'description' => '機台每分鐘固定上報的狀態大包裝。整合了溫度、韌體版本與機台系統級日誌。', 'description' => '機台每分鐘固定上報的狀態大包裝。整合了溫度與韌體版本。雲端記錄規則:溫度只要有變動 (current !== last) 即記錄日誌。',
'payload_parameters' => [ 'payload_parameters' => [
'temperature' => ['type' => 'number', 'description' => '目前機台溫度'], 'temperature' => ['type' => 'integer', 'description' => '目前機台溫度'],
'firmware_version' => ['type' => 'string', 'description' => 'APP 韌體版本'], 'firmware_version' => ['type' => 'string', 'description' => 'APP 韌體版本'],
], ],
'payload_example' => [ 'payload_example' => [
'temperature' => 20.5, 'temperature' => 20,
'firmware_version' => '2.1.6' 'firmware_version' => '2.1.6'
], ],
], ],

View File

@ -1,108 +0,0 @@
# IoT API 測試與對接文件 (IoT API Testing & Documentation)
本文件紀錄 Star Cloud IoT API 的測試紀錄與對接規格,供後續開發與測試追蹤。
---
## 🔐 B000: 維運人員登入認證 (Technician Login)
機台引導階段 (Provisioning) 的第一步,用於核發臨時身份 Token 以便後續下載敏感設定。
### 1. API 資訊
- **Endpoint**: `POST /api/v1/app/admin/login/B000`
- **認證方式**: 無 (需傳入 `username`, `password`, `machine`)
- **回應內容**: `token` (Sanctum Token)
### 2. 回應範例
```json
{
"message": "Success",
"token": "3|abcdef1234567890..."
}
```
---
## 🔑 B014: 機台參數與金鑰下載 (Config Download)
下載機台運作所需的支付金鑰、電子發票設定與正式通訊 Token。
### 1. API 資訊
- **Endpoint**: `POST /api/v1/app/machine/setting/B014`
- **認證方式**: **Bearer Token** (需帶上 B000 取得的 Token)
- **Header**: `Authorization: Bearer {token}`
### 2. 請求參數
- `machine`: 機台序號 (Serial No)
### 3. 回應規格 (欄位映射)
| 欄位 | 說明 | 來源範例 |
| :--- | :--- | :--- |
| `t050v01` | 機台序號 | `SN2026041301` |
| `api_token` | **機台正式 Token** | 後續 B010/B600 認證用 |
| `t050v41` | 玉山特店編號 | `ESUN_STORE_ID` |
| `t050v43` | 玉山 Hash Key | `ESUN_HASH` |
| `t050v34` | 發票特店 ID | `INV_MID` |
| `TP_APP_ID` | 趨勢支付 AppID | `TP_APP_ID` |
### 4. 回應範例 (JSON)
```json
{
"success": true,
"code": 200,
"data": [
{
"t050v01": "SN2026041301",
"api_token": "mac_token_...",
"t050v41": "8081234567",
"t050v42": "9001",
"t050v43": "password123",
"t050v34": "2000132",
"TP_APP_ID": "GREEN_001",
"...": "..."
}
]
}
```
---
## 🔵 B600: 交易紀錄上報 (Transaction Record)
當機台端完成交易(收款或扣點成功)後上傳。
### 1. API 資訊
- **Endpoint**: `POST /api/v1/app/B600`
- **認證方式**: Bearer Token
- **處理方式**: 異步處理 (Redis Queue)。
### 2. 請求範例 (JSON)
```json
{
"flow_id": "F123456789",
"total_amount": 100,
"pay_amount": 100,
"payment_type": 1,
"items": [
{
"product_id": 1,
"product_name": "Test Product",
"price": 50,
"quantity": 2
}
]
}
```
---
## 📝 測試紀錄 (Test Logs)
### 2026-03-16: B600 交易功能測試
- **測試機台**: `SN001`
- **測試方式**: `curl` 命令模擬
- **驗證項目**:
- [ ] HTTP 回傳 `202`
- [ ] 資料庫 `orders` 產生紀錄
- [ ] 資料庫 `order_items` 產生紀錄
- **結果**: 準備中...

View File

@ -1,71 +0,0 @@
# B017 API (遠端改庫存) 技術規範與執行指南
本文件記錄 B017 API 的業務邏輯與技術執行細節,用於指導後端管理系統遠端調整機台庫存的實作。
---
## 1. 業務情境與觸發機制 (Trigger)
### 1.1 觸發流程
1. **管理行為**:管理員在後台管理介面手動修改特定機台的貨道庫存數量。
2. **指令預備**:後端系統將異動請求寫入 `remote_commands` 表 (type: `stock_update`, payload 含貨道編號與數量),並在該機台的 B010 (心跳) 回應中將 `status` 設為 `49` (reload B017)。
3. **機台拉取**:機台收到心跳回應中的 `49` 代碼後,主動呼叫 B017 API (`/api/app/machine/reload_msg/{workid}`)。
4. **數據同步**:機台獲取最新庫存數據並更新本地狀態。
### 1.2 更新範圍
* **全量更新 (Full Sync)**B017 API 回傳該機台**所有貨道**的最新庫存數量,而非僅回傳變動部分。這有助於機台軟體進行「最終狀態一致性」的自我校正。
---
## 2. 資料一致性與日誌 (Consistency & Audit)
### 2.1 更新確認機制
* **隱式成功 (Implicit Success)**:機台更新本地庫存後不需要回傳明確的 B017 確認 API。
* **驗證方式**:系統將以機台下次呼叫 B010 回報的狀態或機台後續的正常銷售紀錄,視為前次更新成功。
### 2.2 管理員異動日誌 (Audit Log)
* **強制要求**:每次後台發起遠端改庫存操作,必須記錄於 `activity_logs` 或特定的 `stock_adjustment_logs`
* *紀錄內容*:管理者 ID、執行時間、機台 ID、調整前後的貨道庫存對照。
### 2.3 庫存衝突處理
* *情境*:若雲端下發庫存更新時,機台現場同時發生實體銷售。
* *處理原則***詳見下方待確認事項**。
---
## 3. 執行面優化 (Execution Strategy)
### 3.1 Redis 緩衝與效能
* **快取策略**:機台的貨道結構與庫存數據應快取於 Redis 中。當管理員調整庫存時,同步更新 Redis 與 MySQL。
* **讀取優化**B017 API 應直接從 Redis 讀取數據回傳給機台,以降低 MySQL 的查詢壓力。
---
## 4. 欄位定義與對照
| B017 欄位 | 資料庫對應 | 說明 |
|----------|------------|------|
| `channelid` | `machine_slots.slot_no` | 貨道編號 |
| `stock` | `machine_slots.stock` | 該貨道的最新庫存數量 |
---
## 5. 開發建議
* 建立一個專屬的 `StockService` 來處理 Redis 與 DB 的同步,以及異動日誌的寫入。
* 在 `B010` 的回應邏輯中,檢查 `remote_commands` 是否有 pending 的庫存異動指令,以動態切換 `status: 49`
---
## 6. 庫存衝突處理原則
**以機台實際出貨為最終依據(最終一致性策略)**。
* **情境**:雲端下發庫存更新(如設為 50同時機台現場發生實體銷售賣出 2 個)。
* **處理原則**:機台在本地執行 `50 - 已出貨量 = 48`,並透過後續的 B602 出貨紀錄上報給雲端,雲端以此為最終庫存數字。
* **理由**:機台掌握實際出貨資訊,強制以雲端覆蓋將導致帳面庫存與實際庫存不符,引發空貨道但系統顯示有庫存的嚴重問題。
---
## 7. 待確認事項 (已結案)
所有關鍵決策已於 2026-03-12 完成確認。

View File

@ -1,67 +0,0 @@
# B055 API (遠端出貨) 技術規範與執行指南
本文件定義 B055 API 的技術實作規範,用於處理從雲端發起的機台遠端出貨指令及其結果回饋。
---
## 1. 業務流程與通訊機制
### 1.1 指令下發流程
1. **指令生成**Cloud 後端(管理者)在 `remote_commands` 表中建立一筆類型為 `dispense`、狀態為 `pending` 的指令。
2. **心跳通知**:在該機台的 B010 回應中,將 `status` 設為 `85` (reload B055)。
3. **機台撈取 (POST)**:機台呼叫 B055 POST API (`/api/app/machine/dispense/{workid}`) 取得詳細出貨參數(貨道 ID、指令 ID
4. **執行與回報 (PUT)**:機台嘗試出貨後,呼叫 B055 PUT API 回報出貨結果及剩餘庫存。
### 1.2 數據關聯 (Data Linking)
* **出貨紀錄整合**:遠端出貨成功後,系統必須在 `dispense_records` 表中同步存入一筆紀錄,並將 `source` 標記為 `remote`(或對應識別),確保後台報表包含所有實體出貨數據。
---
## 2. 指令資料定義:使用全域 `remote_commands`
遠端出貨指令統一存放在 `remote_commands` 表中B055 的特定參數將存放於 `payload` (JSON) 欄位。
| 欄位 | B055 對應說明 | 備註 |
|------|--------------|------|
| `id` | 執行命令 ID | 用於追蹤單次指令 (POST 回傳) |
| `payload->slot_no` | 貨道 ID | 指派機台出貨的通道 |
| `status` | 執行狀態 | pending/sent/success/failed |
| `ttl` | 指令效期 | 預設 60 秒 |
---
## 3. 執行面優化 (Execution Strategy)
* **Redis 隊列**:由於遠端出貨屬於高優先權操作,待執行指令應快取於 Redis 中,以確保機台呼叫 POST 時能即時回應。
* **異步處理**:機台 PUT 回報結果後,系統透過隊列更新 DB 狀態,並根據結果觸發後續邏輯(如扣除庫存、異動日誌)。
---
## 4. 欄位定義與對照
| B055 欄位 | 資料庫對應 | 說明 |
|----------|------------|------|
| `command_id` | `remote_dispense_commands.id` | 指令唯一識別碼 |
| `slot_no` | `machine_slots.slot_no` | 目標貨道編號 |
---
## 5. 業務邏輯細節建議
### 5.1 指令效期 (TTL)
* **建議設定****60 秒 (1 分鐘)**。
* **理由**:由於遠端出貨指令由管理員手動發起(通常用於現場故障排除或客訴處理),若機台因網路問題超過一分鐘未拉取指令,該次操作的情境通常已失效,應自動失效以防止後續無預警出貨。
### 5.2 失敗處理
* **機制**:機台回報出貨失敗時,系統僅需記錄結果並在後台顯示異常狀態。
* **處理原則**:考量目前僅供管理員手動發起,**不需執行自動退款**,由管理員依據日誌進行後續手動處理即可。
### 5.3 安全與權限
* **驗證機制**:維持 `api_token` 驗證,不需額外的加密簽名。
* **權限控制**:建議在 RBAC 權限系統中,限制僅「高級管理員」或「維修主管」具備發起遠端出貨的權限。
---
## 6. 待確認事項 (已結案)
所有關鍵決策已於 2026-03-12 完成確認。

View File

@ -1,60 +0,0 @@
# B220 API (零錢機庫存變動) 技術規範與執行指南
本文件定義 B220 API 的技術實作規範,用於監控機台內的硬幣與鈔票庫存數量,並提供補幣紀錄與找零水位告警功能。
---
## 1. 業務情境與數據特性
### 1.1 數據採集
* **回報內容**機台定期回報各面額1, 5, 10, 50, 100, 500, 1000 元)的**當前累積總量 (Total Amount)**。
* **操作識別**
* `account = 0`:消費者交易引起的庫存變動。
* `account != 0`:管理員或補貨員進行手動補幣或清空錢箱的操作。
### 1.2 報表與對帳
* **零錢庫存分析**:系統必須保留所有變動紀錄,以提供後台「分析報表 > 零錢庫存分析」選單顯示歷史庫存走勢與補幣紀錄。
---
## 2. 執行面優化與告警 (Execution & Alerts)
### 2.1 數據處理頻率
* **回報時機**:機台在**每次交易完成後**(包含消費者找零或管理員補幣)即時呼叫 B220 API 回報最新庫存。
* **快取策略**:機台各面額的最新總量應快取於 Redis 或更新至 `machines` 表的擴充欄位,供後台儀表板即時監看。
* **寫入過濾**:為節省儲存空間,僅在資料數值有實際變動時才寫入 `coin_inventory_logs`
### 2.2 找零不足告警 (Low Inventory Warning)
當特定面額低於預設的水位閾值時,系統將觸發告警。
#### **建議水位告警計畫 (Threshold Plan)**
| 面額 | 告警閾值 (枚數) | 說明 |
|------|----------------|------|
| 1 元 | < 50 | 找零頻率高需預留較快補幣空間 |
| 5 元 | < 40 | |
| 10 元| < 40 | 主要找零面額需重點監控 |
| 50 元| < 20 | |
| 100 元以上 | — | 通常不作為找零面額,僅供庫存監控 |
* **通知管道**
1. **後台儀表板**:在機台管理介面顯示顯眼的「紅字告警」。
2. **系統推播**:即時發送通知給相關營運或補貨人員。
---
## 3. 資料庫結構:`coin_inventory_logs`
| 欄位 | 說明 | 備註 |
|------|------|------|
| `machine_id` | 機台關聯 ID | |
| `value_x` | 各面額數量 | 包含 1, 5, 10, 50, 100, 500, 1000 |
| `account` | 操作人識別 | 0 為消費者,其餘為管理員 |
| `created_at` | 紀錄時間 | |
---
## 4. 待確認事項 (已結案)
所有關鍵決策已於 2026-03-12 完成確認並由系統規劃水位計畫。

View File

@ -1,62 +0,0 @@
# B600 API (消費金流回傳) 技術規範與執行指南
本文件定義 B600 API 的技術實作規範,這是系統處理金流交易、訂單生成與跨 API 關聯的核心。
---
## 1. 交易生命週期與 `flow_id` 串聯
為確保一筆交易數據的完整性,系統採用 `flow_id` 串聯三個連續上報的 API
1. **B600 (金流上報)**
* 機台於支付成功後即時呼叫。
* 雲端收到後產出專屬的 `flow_id` 並回傳給機台。
2. **B601 (發票上報)**
* 機台開立發票後,攜帶 B600 取得的 `flow_id` 呼叫。
* 系統根據 `flow_id` 將發票資訊與 `orders` 表關聯。
3. **B602 (出貨上報)**
* 機台實體出貨後,攜帶 `flow_id` 呼叫。
* 系統根據 `flow_id` 更新訂單的出貨狀態。
---
## 2. 金流類型與標準化
### 2.1 類型定義
根據 B600 PDF 文件規範 (Req3),系統優先採用以下預定義類型進行處理與對帳。
### 2.2 防重複上報 (Idempotency)
為防止網路不穩導致機台重複呼叫 B600系統必須使用機台產生的 `order_no (tid)` 結合 `machine_id` 作為唯一鍵值校驗。
---
## 3. 執行面優化:異步隊列 (Async Queue)
在高併發環境下為確保機台端不因等待資料庫寫入而時耗B600 採以下流程:
1. **即時回應**API 接收請求後,將原始 JSON 派發至 **Redis Queue**
2. **背景處理**:由 `ProcessTransaction` 任務異步執行:
* 建立 `orders``order_items`
* 計算並異動會員點數/優惠券。
* 同步更新儀表板統計數據。
3. **異步回傳**:機台呼叫 API 後會收到 `202 Accepted``flow_id` 則透過回應檔同步回傳。
---
## 4. 數據完整性與異常處理 (Integrity & Exceptions)
### 4.1 自動對帳機制 (Auto-Reconciliation)
* **機制**系統後台需建立定期定時任務Scheduled Task比對同一 `flow_id` 下的以下數據:
- **B600 (收取金額)**:訂單總計金額。
- **B602 (出貨總價值)**:機台實際出貨商品之總價值。
* **異常標記**:若兩者金額不符(例如:付了 $50 但僅出貨 $0 或 $30系統應自動將該訂單標記為 **「金額異常」**,並同步產出警示通知供營運人員手動稽核。
### 4.2 出貨失敗處理 (Dispense Failure)
* **原則**:若 B602 回報「出貨失敗」,系統**不觸發自動退款**程序。
* **處理流程**:系統僅記錄失敗狀態並維持訂單為「待處理/異常」,後續由客服或管理員介入,根據實際情況進行人工退款、補發指令或現場處理。
---
## 5. 待確認事項 (已結案)
所有關鍵決策已於 2026-03-12 完成確認。

View File

@ -1,49 +0,0 @@
# B601 API (發票資訊回傳) 技術規範與執行指南
本文件定義 B601 API 的技術實作規範,用於紀錄每筆訂單對應的電子發票資訊。
---
## 1. 業務流程與角色定義
### 1.1 系統角色
* **機台端**:負責與電子發票系統對接、開立發票,並於取得發票號碼後將資訊回傳至雲端。
* **雲端 (Star Cloud)**:僅負責**紀錄 (Logging)** 發票資訊,不涉及上傳傳財政部平台或寄送中獎通知。
### 1.2 串聯機制
* **關聯 ID**:必須攜帶 B600 取得的 `flow_id`,以便將發票資訊正確掛載至對應的 `orders`
---
## 2. 資料庫結構:`invoices`
為符合台灣發票規範,除了原始 PDF 欄位外,本系統將擴充相關稅務欄位:
| 欄位 | 說明 | 備註 |
|------|------|------|
| `order_id` | 訂單關聯 ID | 透過 `flow_id` 對應 |
| `invoice_no` | 發票號碼 | 例AB-12345678 |
| `invoice_date` | 發票日期 | |
| `random_number` | 隨機碼 | 4 位數 |
| `love_code` | 愛心碼 | 捐贈碼 |
| `business_tax_id` | **公司統編** | 8 位數 |
| `carrier_id` | **載具編號** | 手機條碼或自然人憑證 |
| `carrier_type` | **載具類型** | |
| `status` | **發票狀態** | 1:有效, 0:作廢 |
---
## 3. 發票作廢機制 (Invoice Voiding)
* **需求確認**:當交易發生退款或特定失敗情境時,機台端需具備回報「發票作廢」的能力。
* **API 實作**
* **方法一**:呼叫 B601 POST 時帶入 `status = 0`
* **方法二**:新增 `/api/app/B601/void` 專屬端點(待後續細化)。
---
## 4. 待確認事項 (已結案)
所有關於發票關聯邏輯與重試機制的技術決策已於 2026-03-12 完成確認:
1. **發票缺失標記**:系統不需針對 B600 與 B601 的到達時差進行「發票缺失」自動標記。
2. **重試機制**:機台端呼叫失敗時不需額外的緩存與重傳策略(維持基本回報邏輯)。

View File

@ -1,57 +0,0 @@
# B602 API (出貨回傳) 技術規範與執行指南
本文件定義 B602 API 的技術實作規範,用於紀錄機台實體出貨結果,並作為訂單完結與庫存校準的依據。
---
## 1. 業務邏輯與交易完結
### 1.1 串聯機制
* **關聯 ID**:必須攜帶 B600 取得的 `flow_id`,以確保出貨紀錄正確鏈結至對應的 `orders`
### 1.2 多品項交易處理 (Multi-item Orders)
* **一對多關係**:系統支持一筆訂單(同一個 `flow_id`)對應多筆 `dispense_records`
* **上報方式**:機台每完成一個貨道之出貨即呼叫一次 B602。雲端後端將自動彙整所有關聯之出貨紀錄以計算該訂單之總體狀態。
---
## 2. 庫存一致性機制 (Stock Consistency)
為確保雲端與機台端的物理庫存同步,系統採以下策略:
1. **強制信任機制**:系統將「強制信任」機台在 B602 中回報的 `remaining_stock` (絕對值),並直接更新資料庫中的貨道庫存。
2. **異常監控**:若機台回報的庫存與系統計算的預期值(原值 - 1不符時系統應於後台標記該次出貨為「庫存異動異常」供管理員檢查是否發生卡貨或漏貨。
---
## 3. 資料庫結構:`dispense_records`
| 欄位 | 說明 | 備註 |
|------|------|------|
| `order_id` | 訂單 ID | 透過 `flow_id` 關聯 |
| `product_id` | 商品 ID | 出貨之商品 |
| `slot_no` | 貨道編號 | 出貨之貨道 |
| `dispense_status` | 出貨狀態 | 0: 成功, 1: 失敗 |
| `remaining_stock` | 剩餘庫存 | 機台回報之絕對值 |
| `source` | 來源標記 | `local` (B602), `remote` (B055) |
---
## 4. 數據完整性與異常處理 (Integrity & Exceptions)
### 4.1 出貨超時監控 (Dispense Timeout)
* **閾值建議**:若系統收到 B600 (金流) 超過 **5 分鐘** 仍未收到任何對應之 B602 (出貨回報),雲端後台應自動將該訂單標記為 **「出貨超時」**。
* **用途**:此狀態主要用於輔助對帳,標記可能發生網路中斷或機台異常卡死之交易,需由管理員介入核實。
### 4.2 出貨失敗處理 (Dispense Failure)
* **狀態遷移**:當 B602 回報 `dispense_status = 1` (失敗) 時,訂單狀態應更新為 **「出貨失敗」**。
* **即時通知**:系統應立即產出 **「後台警示通知」** 並推播給營運人員,以便快速處理現場排礙或後續補償。
* **補償與維護策略 (現階段)**
- **點數/優惠券**:目前不採自動退回機制,維持異常狀態由管理員人工處理。
- **營運維護**:目前暫不自動產出維護工單或推播給補貨員,僅紀錄於異常日誌並顯示於後台通知區。
---
## 5. 待確認事項 (已結案)
所有關於出貨流程與超時判定之技術決策已於 2026-03-12 完成確認。

View File

@ -1,46 +0,0 @@
# [暫緩執行] B650 API (會員/點數/取貨碼驗證) 技術規範與執行指南
本文件定義 B650 API 的技術實作規範,用於處理會員驗證、點數折抵、優惠券兌換、取貨碼驗證以及線上訂單線下結帳等多元功能。
---
## 1. 業務功能與驗證類型 (req6)
根據 API 請求中的 `req6` 欄位B650 提供以下驗證模式:
| req6 | 說明 | 核心流程 |
|------|------|----------|
| `0` | 點擊商品驗證 | 驗證會員資格與點數餘額 |
| `1` | 會員純驗證 | 僅確認會員身份是否有效 |
| `2` | 會員兌換商品清單 | 撈取該會員可使用的兌換券或贈品清單 |
| `3` | 線上訂單線下結帳 | 驗證取貨碼或訂單編號 |
| `4` | 會員贈品 | 處理行銷活動贈品領取 |
---
## 2. 執行面機制 (Execution Strategy)
* **高可用性請求**由於會員驗證通常發生在交易前夕API 必須具備極速響應能力,建議與第三方系統(若有)採用高效能通訊協議。
* **Response 狀態碼校驗**:系統必須嚴格遵守 PDF 定義的狀態碼(如 `101` 全額折抵, `104` 線上訂單驗證成功, `30` 會員使用中等)進行前端反饋。
---
## 3. 待確認事項 (To-be-confirmed)
以下議題目前皆列為**待確認**,將於 Phase 2 或後續討論中釐清:
### 3.1 會員系統架構 (Architecture)
1. **資料主體**Star Cloud 是直接管理會員資料庫,還是僅作為轉發請求至第三方 CRM 的代理伺服器?
### 3.2 安全與防雙重消費 (Security)
2. **鎖定機制**:是否需要在 Redis 實作「會員操作 Session」防止同一帳號在多台機台同時扣點 (狀態碼 30)
3. **Token 時效**:回傳之 `userToken` 的有效期與刷新機制。
### 3.3 線上/線下整合流程
4. **訂單轉換**:驗證取貨碼成功後,雲端是否應自動產出對應的銷售紀錄單?
### 3.4 扣點邏輯
5. **預扣 vs 實扣**:點數與優惠券應於 B650 驗證時預先鎖定,還是待 B602 出貨成功後才正式扣除?
### 3.5 界面展示需求
6. **商品資訊**:當 `req6 = 2` 時,回傳的產品清單是否需要包含圖片或詳細描述供機台屏幕顯示?

View File

@ -1,49 +0,0 @@
# [暫緩執行] B710 API (計時器狀態回傳) 技術規範與執行指南
本文件定義 B710 API 的技術實作規範,用於處理計時型設備(如洗衣服務、按摩服務)的剩餘時間監測與狀態回報。
---
## 1. 業務流程與資料流
### 1.1 狀態回報
* **回報內容**:機台定期回報特定貨道 (product/timer) 的 `remaining_seconds` (剩餘秒數) 與目前狀態。
* **資料用途**
* **即時監控**:供管理後台儀表板顯示設備的使用進度。
* **稼動分析**:紀錄設備的總租用時數與閒置率。
---
## 2. 執行面機制 (Execution Strategy)
* **異步處理**:由於計時狀態可能高頻回報,系統應優先將數據更新至 **Redis 快取**,以確保前端查詢的高效能與資料庫的負擔平衡。
---
## 3. 資料庫結構:`timer_statuses`
| 欄位 | 說明 | 備註 |
|------|------|------|
| `machine_id` | 機台 ID | |
| `product_id` | 產品/服務 ID | |
| `remaining_seconds` | 剩餘秒數 | |
| `status` | 運行狀態 | 1:運行中, 0:停止 |
---
## 4. 待確認事項 (To-be-confirmed)
以下議題目前皆列為**待確認**,將於後續開發階段釐清:
### 4.1 回報頻率與快取
1. **更新頻率**:確認機台呼叫 B710 的精確頻率,以及資料庫寫入的節流策略(例如:僅在關鍵變動或每分鐘寫入一次)。
### 4.2 遠端通知與異常
2. **結束通知**:倒數結束時,雲端是否需主動對對消費者發送推播或系統訊息?
3. **斷線判定**:針對長期未回報的計時器,雲端應自動判定為結束還是異常?
### 4.3 復歸機制
4. **斷電補償**:機台意外斷電重啟後,是否需向雲端 B010 撈回最後的剩餘秒數以恢復服務?
### 4.4 報表需求
5. **稼動分析**:後台是否需要針對此數據計算「設備利用率」等績效指標。

View File

@ -77,11 +77,6 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
// 交易 (B600) — B601/B602 已遷移至 MQTT transaction Topic // 交易 (B600) — B601/B602 已遷移至 MQTT transaction Topic
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::prefix('machines')->group(function () {
Route::post('/{id}/logs', [\App\Http\Controllers\Api\V1\MachineController::class, 'storeLog']);
}); });
}); });