diff --git a/app/Http/Controllers/Admin/MachineController.php b/app/Http/Controllers/Admin/MachineController.php index 6ac1339..fe8d57b 100644 --- a/app/Http/Controllers/Admin/MachineController.php +++ b/app/Http/Controllers/Admin/MachineController.php @@ -2,10 +2,15 @@ namespace App\Http\Controllers\Admin; +use App\Jobs\Product\SendProductSyncCommandJob; use App\Models\Machine\Machine; +use App\Models\Machine\MachineProductPrice; +use App\Models\Product\Product; use App\Models\System\Company; use App\Services\Machine\MachineService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\View\View; class MachineController extends AdminController @@ -257,6 +262,91 @@ class MachineController extends AdminController } } + /** + * 機台專屬定價頁:列出該機台「所屬公司的完整商品目錄」(非僅貨道商品), + * 可在上貨前先把機台價/會員價訂好。未填=沿用全域 products 價。 + */ + public function pricing(Machine $machine): View + { + $products = Product::where('company_id', $machine->company_id) + ->active() + ->orderBy('name') + ->get(['id', 'name', 'image_url', 'barcode', 'price', 'member_price']); + + $overrides = $machine->productPrices()->get()->keyBy('product_id'); + + $items = $products->map(function (Product $p) use ($overrides) { + $ov = $overrides->get($p->id); + return [ + 'product_id' => $p->id, + 'name' => $p->name, + 'image_url' => $p->image_url, + 'barcode' => $p->barcode, + 'global_price' => (float) $p->price, + 'global_member_price' => $p->member_price !== null ? (float) $p->member_price : null, + 'override_price' => $ov && $ov->price !== null ? (float) $ov->price : null, + 'override_member_price' => $ov && $ov->member_price !== null ? (float) $ov->member_price : null, + ]; + })->values(); + + return view('admin.machines.pricing', compact('machine', 'items')); + } + + /** + * 批次儲存機台專屬定價。 + * + * - price/member_price 皆空 → 刪除覆蓋,回到全域價。 + * - 任一有值 → 建立/更新覆蓋;會員價於 B012 下發時自動夾為不高於機台售價。 + * - 用 quiet 寫入避免逐筆觸發 observer 重複推送,最後統一推一次 B012。 + * - 商品須屬於該機台公司(跨租戶防護)。 + */ + public function updatePricing(Request $request, Machine $machine): \Illuminate\Http\JsonResponse + { + $validated = $request->validate([ + 'items' => 'required|array', + 'items.*.product_id' => 'required|integer|exists:products,id', + 'items.*.price' => 'nullable|numeric|min:1|max:99999999', + 'items.*.member_price' => 'nullable|numeric|min:1|max:99999999', + ]); + + $allowedProductIds = Product::where('company_id', $machine->company_id) + ->pluck('id') + ->flip(); + + DB::transaction(function () use ($validated, $machine, $allowedProductIds) { + foreach ($validated['items'] as $item) { + $productId = (int) $item['product_id']; + if (!$allowedProductIds->has($productId)) { + continue; + } + + $price = isset($item['price']) && $item['price'] !== '' ? (float) $item['price'] : null; + $memberPrice = isset($item['member_price']) && $item['member_price'] !== '' ? (float) $item['member_price'] : null; + + if ($price === null && $memberPrice === null) { + $machine->productPrices()->where('product_id', $productId)->delete(); + continue; + } + + $row = MachineProductPrice::firstOrNew([ + 'machine_id' => $machine->id, + 'product_id' => $productId, + ]); + $row->price = $price; + $row->member_price = $memberPrice; + $row->saveQuietly(); + } + }); + + // 統一推一次 B012 同步給該機台(覆蓋於請求當下即時套用,無需失效公司快取) + SendProductSyncCommandJob::dispatch($machine->id, __('Machine pricing updated'), Auth::id()); + + return response()->json([ + 'success' => true, + 'message' => __('Machine pricing saved and sync command pushed.'), + ]); + } + /** * 取得機台統計數據 (AJAX) */ diff --git a/app/Http/Controllers/Api/V1/App/MachineController.php b/app/Http/Controllers/Api/V1/App/MachineController.php index e0b6ab3..0c08126 100644 --- a/app/Http/Controllers/Api/V1/App/MachineController.php +++ b/app/Http/Controllers/Api/V1/App/MachineController.php @@ -394,8 +394,8 @@ class MachineController extends Controller { $machine = $request->get('machine'); - // Cache First: Get from cache or rebuild if missing - $payload = $catalogService->getPayload($machine->company_id); + // 公司基底目錄(快取) + 該機台專屬定價覆蓋(即時套用,見 getMachinePayload) + $payload = $catalogService->getMachinePayload($machine); return response()->json($payload); } diff --git a/app/Models/Machine/Machine.php b/app/Models/Machine/Machine.php index 369d505..e69ed23 100644 --- a/app/Models/Machine/Machine.php +++ b/app/Models/Machine/Machine.php @@ -54,6 +54,14 @@ class Machine extends Model ])) { app(\App\Services\Machine\MachineService::class)->syncMachineSlotMaxStock($machine); } + + // 機台轉移公司:不同公司商品 ID 體系不同,舊的機台專屬定價必須清空, + // 避免跨租戶價格錯位與資料污染。批次刪除不觸發 model 事件,故補推一次 + // B012 同步,讓機台重抓新公司目錄與(已清空的)定價。 + if ($machine->wasChanged('company_id')) { + $machine->productPrices()->delete(); + \App\Jobs\Product\SendProductSyncCommandJob::dispatch($machine->id, __('Machine company changed')); + } }); } @@ -401,6 +409,11 @@ class Machine extends Model return $this->hasMany(MachineSlot::class); } + public function productPrices() + { + return $this->hasMany(MachineProductPrice::class); + } + public function commands() { return $this->hasMany(RemoteCommand::class); diff --git a/app/Models/Machine/MachineProductPrice.php b/app/Models/Machine/MachineProductPrice.php new file mode 100644 index 0000000..a38864d --- /dev/null +++ b/app/Models/Machine/MachineProductPrice.php @@ -0,0 +1,58 @@ + 'decimal:2', + 'member_price' => 'decimal:2', + ]; + + protected static function booted(): void + { + static::saved(fn (self $row) => $row->dispatchSync()); + static::deleted(fn (self $row) => $row->dispatchSync()); + } + + /** + * 推送 B012 同步指令給該機台,讓它即時重抓最新定價。 + * 控制台/離線判定沿用 SendProductSyncCommandJob 既有邏輯。 + */ + protected function dispatchSync(): void + { + SendProductSyncCommandJob::dispatch($this->machine_id, __('Machine pricing updated')); + } + + public function machine() + { + return $this->belongsTo(Machine::class); + } + + public function product() + { + return $this->belongsTo(Product::class); + } +} diff --git a/app/Services/Product/ProductCatalogService.php b/app/Services/Product/ProductCatalogService.php index 204db9d..cfdc1e1 100644 --- a/app/Services/Product/ProductCatalogService.php +++ b/app/Services/Product/ProductCatalogService.php @@ -2,6 +2,8 @@ namespace App\Services\Product; +use App\Models\Machine\Machine; +use App\Models\Machine\MachineProductPrice; use App\Models\Product\Product; use App\Models\System\Company; use Illuminate\Support\Facades\Cache; @@ -76,6 +78,54 @@ class ProductCatalogService Cache::forget($this->getCacheKey($companyId)); } + /** + * 取得「該機台」的商品目錄:公司基底目錄(快取) + 機台專屬定價覆蓋。 + * + * 設計取捨:重的 i18n 目錄留在 per-company 快取;機台覆蓋於請求當下即時套用, + * 不另設機台快取。覆蓋只是一次 (machine_id) 的索引查詢,極輕量;且改價僅推「該台」 + * 重抓,不會造成多台群湧。如此完全避開「機台快取失效 / 公司基底變動連帶失效」的複雜度。 + * + * @param Machine $machine + * @return array + */ + public function getMachinePayload(Machine $machine): array + { + $payload = $this->getPayload($machine->company_id); + + $overrides = MachineProductPrice::where('machine_id', $machine->id) + ->get(['product_id', 'price', 'member_price']) + ->keyBy('product_id'); + + if ($overrides->isEmpty()) { + return $payload; + } + + // PHP 陣列為值複製,array_map 產生新陣列,不會污染 Cache 內的公司基底目錄。 + $payload['data'] = array_map(function ($item) use ($overrides) { + $override = $overrides->get((int) ($item['t060v00'] ?? 0)); + if (!$override) { + return $item; + } + + // 機台售價(t063v03=App machinePrice):有覆蓋價就用,否則維持全域售價基底。 + $sellingPrice = $override->price !== null + ? (float) $override->price + : (float) $item['t063v03']; + $item['t063v03'] = $sellingPrice; + + // 機台會員價(t060v30):有覆蓋就用,否則沿用全域會員價; + // 一律夾為「不高於機台售價」,避免會員買得比一般人貴。 + $memberPrice = $override->member_price !== null + ? (float) $override->member_price + : (float) $item['t060v30']; + $item['t060v30'] = min($memberPrice, $sellingPrice); + + return $item; + }, $payload['data']); + + return $payload; + } + /** * Map a Product model to the catalog format expected by machines. * diff --git a/database/migrations/2026_06_23_000001_create_machine_product_prices_table.php b/database/migrations/2026_06_23_000001_create_machine_product_prices_table.php new file mode 100644 index 0000000..817f9b5 --- /dev/null +++ b/database/migrations/2026_06_23_000001_create_machine_product_prices_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('machine_id')->constrained('machines')->onDelete('cascade'); + $table->foreignId('product_id')->constrained('products')->onDelete('cascade'); + $table->decimal('price', 10, 2)->nullable()->comment('機台專屬售價;NULL=用全域 products.price'); + $table->decimal('member_price', 10, 2)->nullable()->comment('機台專屬會員價;NULL=用全域 member_price(並夾為不高於機台售價)'); + $table->timestamps(); + + // 一台機台對同一商品只有一筆覆蓋價 + $table->unique(['machine_id', 'product_id']); + }); + } + + public function down(): void + { + Schema::dropIfExists('machine_product_prices'); + } +}; diff --git a/lang/en.json b/lang/en.json index 64349cc..9cce327 100644 --- a/lang/en.json +++ b/lang/en.json @@ -765,6 +765,7 @@ "Failed to get invoice print URL": "Failed to get invoice print URL", "Failed to load permissions": "Failed to load permissions", "Failed to load preview": "Failed to load preview", + "Failed to load pricing": "Failed to load pricing", "Failed to load tab content": "Failed to load tab content", "Failed to resolve issues.": "Failed to resolve issues.", "Failed to save permissions.": "Failed to save permissions.", @@ -897,6 +898,7 @@ "Generate Replenishment Order": "Generate Replenishment Order", "Generate and manage one-time pickup codes for customers": "Generate and manage one-time pickup codes for customers", "Gift Definitions": "Gift Definitions", + "Global": "Global", "Global roles accessible by all administrators.": "Global roles accessible by all administrators.", "Go Authorize": "Go Authorize", "Go Back": "Go Back", @@ -1020,6 +1022,7 @@ "Latitude": "Latitude", "Lease": "Lease", "Leave blank to deploy immediately, or set a future time to schedule a background release.": "Leave blank to deploy immediately, or set a future time to schedule a background release.", + "Leave blank to use the global product price. Member price will be capped at the machine selling price.": "Leave blank to use the global product price. Member price will be capped at the machine selling price.", "Leave empty for permanent code": "Leave empty for permanent code", "Leave empty or 0 for permanent code": "Leave empty or 0 for permanent code", "Leave empty to inherit company settings": "Leave empty to inherit company settings", @@ -1105,6 +1108,8 @@ "Machine Model Settings": "Machine Model Settings", "Machine Name": "Machine Name", "Machine Permissions": "Machine Permissions", + "Machine Price": "Machine Price", + "Machine Pricing": "Machine Pricing", "Machine Reboot": "Machine Reboot", "Machine Registry": "Machine Registry", "Machine Replenishment": "Machine Replenishment", @@ -1123,6 +1128,7 @@ "Machine Stock Movements": "Machine Stock Movements", "Machine System Settings": "Machine System Settings", "Machine Utilization": "Machine Utilization", + "Machine company changed": "Machine company changed", "Machine created successfully.": "Machine created successfully.", "Machine deleted successfully.": "Machine deleted successfully.", "Machine has reconnected to the server and resumed normal heartbeat.": "Machine has reconnected to the server and resumed normal heartbeat.", @@ -1135,6 +1141,8 @@ "Machine normal": "Machine normal", "Machine not found": "Machine not found", "Machine permissions updated successfully.": "Machine permissions updated successfully.", + "Machine pricing saved and sync command pushed.": "Machine pricing saved and sync command pushed.", + "Machine pricing updated": "Machine pricing updated", "Machine settings updated successfully.": "Machine settings updated successfully.", "Machine temperature exceeds limit": "Machine temperature exceeds limit, purchase suspended", "Machine to Warehouse": "Machine to Warehouse", @@ -1338,6 +1346,7 @@ "No maintenance records found": "No maintenance records found", "No matching logs found": "No matching logs found", "No matching machines": "No matching machines", + "No matching products": "No matching products", "No materials available": "No materials available", "No movement records found": "No movement records found", "No movements found": "No movements found", @@ -1348,9 +1357,11 @@ "No pickup codes found": "No pickup codes found", "No product data matching search criteria": "No product data matching search criteria", "No product record found": "No product record found", + "No products available for this company": "No products available for this company", "No products found": "No products found", "No products found in this warehouse": "No products found in this warehouse", "No products found matching your criteria.": "No products found matching your criteria.", + "No products on this machine yet": "No products on this machine yet", "No records found": "No records found", "No related detail found": "No related detail found", "No remark": "No remark", @@ -1827,6 +1838,7 @@ "Sales Today": "Sales Today", "Sales revenue minus product cost net value": "Sales revenue minus product cost net value", "Save": "Save", + "Save & Sync": "Save & Sync", "Save Changes": "Save Changes", "Save Config": "Save Config", "Save Material": "Save Material", @@ -1834,6 +1846,8 @@ "Save Settings": "Save Settings", "Save Version": "Save Version", "Save error:": "Save error:", + "Save failed": "Save failed", + "Saved": "Saved", "Saved.": "Saved.", "Saving...": "Saving...", "Scale level and access control": "Scale level and access control", @@ -2590,6 +2604,7 @@ "of items": "items", "optional": "optional", "orders": "orders", + "overrides set": "overrides set", "pending": "Pending", "permissions": "Permission Settings", "permissions.accounts": "Account Management", diff --git a/lang/ja.json b/lang/ja.json index b12f2e1..92d9939 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -765,6 +765,7 @@ "Failed to get invoice print URL": "領収書印刷URLの取得に失敗しました", "Failed to load permissions": "権限の読み込みに失敗しました", "Failed to load preview": "プレビューの読み込みに失敗しました", + "Failed to load pricing": "価格設定の読み込みに失敗しました", "Failed to load tab content": "タブ内容の読み込みに失敗しました", "Failed to resolve issues.": "異常の解消に失敗しました。", "Failed to save permissions.": "権限の保存に失敗しました。", @@ -897,6 +898,7 @@ "Generate Replenishment Order": "補充伝票生成", "Generate and manage one-time pickup codes for customers": "顧客用のワンタイム受取コードを生成・管理", "Gift Definitions": "ギフト設定", + "Global": "グローバル価格", "Global roles accessible by all administrators.": "全管理者がアクセス可能なグローバルロール。", "Go Authorize": "先に授権する", "Go Back": "戻る", @@ -1020,6 +1022,7 @@ "Latitude": "緯度", "Lease": "リース", "Leave blank to deploy immediately, or set a future time to schedule a background release.": "空欄のままにすると即時配信されます。未来の日時を指定すると背景で自動予約配信されます。", + "Leave blank to use the global product price. Member price will be capped at the machine selling price.": "空欄の場合はグローバル商品価格を使用します。会員価格はマシン販売価格を上限に自動調整されます。", "Leave empty for permanent code": "空欄で無期限コードになります", "Leave empty or 0 for permanent code": "空欄または0で無期限コードになります", "Leave empty to inherit company settings": "空白のままにすると会社のグローバル設定を継承します", @@ -1105,6 +1108,8 @@ "Machine Model Settings": "機器モデル設定", "Machine Name": "機器名", "Machine Permissions": "機器権限", + "Machine Price": "マシン価格", + "Machine Pricing": "マシン価格設定", "Machine Reboot": "機器アプリ再起動", "Machine Registry": "機器リスト", "Machine Replenishment": "機器補充伝票", @@ -1123,6 +1128,7 @@ "Machine Stock Movements": "機器在庫変動履歴", "Machine System Settings": "機器システム設定", "Machine Utilization": "機器稼働率", + "Machine company changed": "マシンの所属会社が変更されました", "Machine created successfully.": "機器が正常に作成されました。", "Machine deleted successfully.": "機器が正常に削除されました。", "Machine has reconnected to the server and resumed normal heartbeat.": "マシンがサーバーに再接続し、通常のハートビートを再開しました。", @@ -1135,6 +1141,8 @@ "Machine normal": "デバイス正常", "Machine not found": "機器が見つかりません", "Machine permissions updated successfully.": "機器権限が正常に更新されました。", + "Machine pricing saved and sync command pushed.": "マシン価格を保存し、同期コマンドを送信しました。", + "Machine pricing updated": "マシン価格を更新しました", "Machine settings updated successfully.": "機器設定が正常に更新されました。", "Machine temperature exceeds limit": "設定温度超過のため購入一時停止", "Machine to Warehouse": "機器から倉庫", @@ -1338,6 +1346,7 @@ "No maintenance records found": "メンテナンス記録が見つかりません", "No matching logs found": "一致するログが見つかりません", "No matching machines": "一致する機器がありません", + "No matching products": "一致する商品がありません", "No materials available": "利用可能な素材がありません", "No movement records found": "変動記録が見つかりません", "No movements found": "変動が見つかりません", @@ -1348,9 +1357,11 @@ "No pickup codes found": "受取コードが見つかりません", "No product data matching search criteria": "検索条件に一致する商品データはありません", "No product record found": "商品記録が見つかりません", + "No products available for this company": "この会社で販売可能な商品がありません", "No products found": "商品が見つかりません", "No products found in this warehouse": "この倉庫に商品は見つかりません", "No products found matching your criteria.": "条件に一致する商品が見つかりません。", + "No products on this machine yet": "このマシンにはまだ商品がありません", "No records found": "記録が見つかりません", "No related detail found": "関連する詳細が見つかりません", "No remark": "備考なし", @@ -1827,6 +1838,7 @@ "Sales Today": "本日の販売数", "Sales revenue minus product cost net value": "販売額から商品コストを差し引いた純利益", "Save": "保存", + "Save & Sync": "保存して同期", "Save Changes": "変更を保存", "Save Config": "設定を保存", "Save Material": "素材を保存", @@ -1834,6 +1846,8 @@ "Save Settings": "設定を保存", "Save Version": "Save Version", "Save error:": "保存エラー:", + "Save failed": "保存に失敗しました", + "Saved": "保存しました", "Saved.": "保存しました。", "Saving...": "保存中...", "Scale level and access control": "スケールレベルとアクセス制御", @@ -2590,6 +2604,7 @@ "of items": "件中", "optional": "任意", "orders": "件", + "overrides set": "件のマシン価格を設定済み", "pending": "機器受取待機中", "permissions": "権限設定", "permissions.accounts": "アカウント管理", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index ae323e7..d377ea5 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -765,6 +765,7 @@ "Failed to get invoice print URL": "取得發票列印網址失敗", "Failed to load permissions": "載入權限失敗", "Failed to load preview": "載入預覽失敗", + "Failed to load pricing": "載入定價失敗", "Failed to load tab content": "載入分頁內容失敗", "Failed to resolve issues.": "排除異常失敗。", "Failed to save permissions.": "無法儲存權限設定。", @@ -897,6 +898,7 @@ "Generate Replenishment Order": "產生補貨單", "Generate and manage one-time pickup codes for customers": "產生並管理客戶的一次性取貨碼", "Gift Definitions": "禮品設定", + "Global": "全域價", "Global roles accessible by all administrators.": "適用於所有管理者的全域角色。", "Go Authorize": "前往授權", "Go Back": "返回", @@ -1020,6 +1022,7 @@ "Latitude": "緯度", "Lease": "租賃", "Leave blank to deploy immediately, or set a future time to schedule a background release.": "留空將立即部署更新,或設定未來時間由背景自動排程發布。", + "Leave blank to use the global product price. Member price will be capped at the machine selling price.": "留空則沿用全域商品價。會員價會自動夾為不高於機台售價。", "Leave empty for permanent code": "留空表示永久有效", "Leave empty or 0 for permanent code": "留空或輸入 0 則為永久代碼", "Leave empty to inherit company settings": "留空則繼承公司全域設定", @@ -1105,6 +1108,8 @@ "Machine Model Settings": "機台型號設定", "Machine Name": "機台名稱", "Machine Permissions": "機台權限", + "Machine Price": "機台價", + "Machine Pricing": "機台定價", "Machine Reboot": "機台 APP 重啟", "Machine Registry": "機台清冊", "Machine Replenishment": "機台補貨單", @@ -1123,6 +1128,7 @@ "Machine Stock Movements": "機台庫存流水帳", "Machine System Settings": "機台系統設定", "Machine Utilization": "機台稼動率", + "Machine company changed": "機台所屬公司已變更", "Machine created successfully.": "機台已成功建立。", "Machine deleted successfully.": "機台已成功刪除。", "Machine has reconnected to the server and resumed normal heartbeat.": "機台已重新連線至伺服器並恢復正常心跳。", @@ -1135,6 +1141,8 @@ "Machine normal": "機台正常", "Machine not found": "找不到機台", "Machine permissions updated successfully.": "機台權限已成功更新。", + "Machine pricing saved and sync command pushed.": "機台定價已儲存並推送同步指令。", + "Machine pricing updated": "機台定價已更新", "Machine settings updated successfully.": "機台設定已成功更新。", "Machine temperature exceeds limit": "機器超過設定溫度,暫停購買", "Machine to Warehouse": "機台對倉庫", @@ -1338,6 +1346,7 @@ "No maintenance records found": "找不到維修紀錄", "No matching logs found": "找不到符合條件的日誌", "No matching machines": "查無匹配機台", + "No matching products": "找不到符合的商品", "No materials available": "沒有可用的素材", "No movement records found": "查無異動紀錄", "No movements found": "尚無異動紀錄", @@ -1348,9 +1357,11 @@ "No pickup codes found": "找不到取貨碼", "No product data matching search criteria": "沒有符合搜尋條件的商品資料", "No product record found": "無商品紀錄", + "No products available for this company": "此公司尚無可販售商品", "No products found": "未找到商品", "No products found in this warehouse": "此倉庫目前無商品庫存", "No products found matching your criteria.": "找不到符合條件的商品。", + "No products on this machine yet": "此機台尚無商品", "No records found": "未找到相關紀錄", "No related detail found": "無關聯詳細資料", "No remark": "尚無備註", @@ -1827,6 +1838,7 @@ "Sales Today": "今日銷售", "Sales revenue minus product cost net value": "銷售額扣除商品成本淨值", "Save": "儲存", + "Save & Sync": "儲存並同步", "Save Changes": "儲存變更", "Save Config": "儲存配置", "Save Material": "儲存素材", @@ -1834,6 +1846,8 @@ "Save Settings": "儲存設定", "Save Version": "儲存版本", "Save error:": "儲存錯誤:", + "Save failed": "儲存失敗", + "Saved": "已儲存", "Saved.": "已儲存", "Saving...": "儲存中...", "Scale level and access control": "層級與存取控制", @@ -2590,6 +2604,7 @@ "of items": "筆項目", "optional": "選填", "orders": "筆", + "overrides set": "項已設定機台價", "pending": "等待機台領取", "permissions": "權限設定", "permissions.accounts": "帳號管理", diff --git a/resources/views/admin/machines/index.blade.php b/resources/views/admin/machines/index.blade.php index 8947f87..e5b8185 100644 --- a/resources/views/admin/machines/index.blade.php +++ b/resources/views/admin/machines/index.blade.php @@ -720,6 +720,15 @@ d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" /> + + + + + @@ -821,6 +830,39 @@

{{ $machine->latest_status_log_time->diffForHumans() }}

@endif +
+

+ {{ __('Hardware Status') }}

+ @php $hStatus = $machine->hardware_status; @endphp +
+ {{-- 下位機 --}} +
+ + {{ __('Sub-machine') }} +
+ {{-- 刷卡機 (信用卡/電子票證/手機支付共用同一台):僅基礎版且啟用刷卡機才顯示 --}} + @if($machine->show_card_terminal) + @php $ctStatus = $machine->card_terminal_status; @endphp +
+ + {{ __('Card Terminal') }} +
+ @endif +
+

@@ -854,6 +896,14 @@ {{ __('Logs') }} + + + + + {{ __('Pricing') }} +

@empty diff --git a/resources/views/admin/machines/pricing.blade.php b/resources/views/admin/machines/pricing.blade.php new file mode 100644 index 0000000..fb35c44 --- /dev/null +++ b/resources/views/admin/machines/pricing.blade.php @@ -0,0 +1,226 @@ +@extends('layouts.admin') + +@section('content') +
+ + {{-- Header --}} +
+ + + + + +
+

+ + + + + + {{ __('Machine Pricing') }} +

+
+ {{ $machine->serial_no }} + + {{ $machine->name }} +
+
+
+ +
+ {{-- Hint + Search --}} +
+

+ {{ __('Leave blank to use the global product price. Member price will be capped at the machine selling price.') }} +

+
+ + + + + + + +
+
+ + {{-- Empty (no products in company) --}} + + + {{-- Table --}} +
+ + + + + + + + + + + + +
{{ __('Product') }}{{ __('Machine Price') }}{{ __('Member Price') }}
+
+
+ + {{-- Sticky Save Bar --}} +
+
+ + + {{ __('Cancel') }} + + +
+
+
+@endsection + +@section('scripts') + +@endsection diff --git a/resources/views/admin/warehouses/machine-inventory.blade.php b/resources/views/admin/warehouses/machine-inventory.blade.php index 48e9832..805c739 100644 --- a/resources/views/admin/warehouses/machine-inventory.blade.php +++ b/resources/views/admin/warehouses/machine-inventory.blade.php @@ -783,6 +783,7 @@ + @endsection diff --git a/resources/views/components/breadcrumbs.blade.php b/resources/views/components/breadcrumbs.blade.php index e854ff5..50c406b 100644 --- a/resources/views/components/breadcrumbs.blade.php +++ b/resources/views/components/breadcrumbs.blade.php @@ -104,6 +104,7 @@ 'logs' => __('Machine Logs'), 'permissions' => __('Machine Permissions'), 'utilization' => __('Utilization Rate'), + 'pricing' => __('Machine Pricing'), 'expiry' => __('Expiry Management'), 'maintenance' => __('Maintenance Records'), 'ui-elements' => __('UI Elements'), diff --git a/routes/web.php b/routes/web.php index a57020c..db10ec5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -68,6 +68,9 @@ Route::middleware(['auth', 'auth.session', 'verified', 'tenant.access'])->prefix Route::get('/utilization-ajax/{id?}', [App\Http\Controllers\Admin\MachineController::class, 'utilizationData'])->name('utilization-ajax'); Route::get('/{machine}/slots-ajax', [App\Http\Controllers\Admin\MachineController::class, 'slotsAjax'])->name('slots-ajax'); Route::post('/{machine}/slots/expiry', [App\Http\Controllers\Admin\MachineController::class, 'updateSlotExpiry'])->name('slots.expiry.update'); + // 機台專屬定價(獨立頁,列全公司目錄,可上貨前先定價) + Route::get('/{machine}/pricing', [App\Http\Controllers\Admin\MachineController::class, 'pricing'])->name('pricing'); + Route::post('/{machine}/pricing', [App\Http\Controllers\Admin\MachineController::class, 'updatePricing'])->name('pricing.update'); Route::get('/{machine}/logs-ajax', [App\Http\Controllers\Admin\MachineController::class, 'logsAjax'])->name('logs-ajax'); Route::get('/{machine}/temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'temperatureAjax'])->name('temperature-ajax'); Route::get('/{machine}/ambient-temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'ambientTemperatureAjax'])->name('ambient-temperature-ajax');