diff --git a/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php b/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php index 042c9c3..b007952 100644 --- a/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php +++ b/app/Http/Controllers/Admin/BasicSettings/MachineSettingController.php @@ -3,17 +3,12 @@ namespace App\Http\Controllers\Admin\BasicSettings; use App\Http\Controllers\Admin\AdminController; -use App\Jobs\Product\SendProductSyncCommandJob; use App\Models\Machine\Machine; use App\Models\Machine\MachineModel; -use App\Models\Machine\MachineProductPrice; -use App\Models\Product\Product; use App\Models\System\Company; use App\Models\System\PaymentConfig; use App\Traits\ImageHandler; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\DB; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Storage; @@ -202,91 +197,6 @@ class MachineSettingController extends AdminController return view('admin.basic-settings.machines.edit', compact('machine', 'models', 'paymentConfigs', 'companies')); } - /** - * 機台專屬定價頁:列出該機台「所屬公司的完整商品目錄」(非僅貨道商品), - * 可在上貨前先把機台價/會員價訂好。未填=沿用全域 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.basic-settings.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.'), - ]); - } - /** * 更新機台詳細參數 */ 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/resources/views/admin/basic-settings/machines/partials/tab-machines.blade.php b/resources/views/admin/basic-settings/machines/partials/tab-machines.blade.php index 3fcdbf2..cea18f6 100644 --- a/resources/views/admin/basic-settings/machines/partials/tab-machines.blade.php +++ b/resources/views/admin/basic-settings/machines/partials/tab-machines.blade.php @@ -149,13 +149,6 @@ - - - - - + + + + + @@ -854,6 +863,14 @@ {{ __('Logs') }} + + + + + {{ __('Pricing') }} + @empty diff --git a/resources/views/admin/basic-settings/machines/pricing.blade.php b/resources/views/admin/machines/pricing.blade.php similarity index 67% rename from resources/views/admin/basic-settings/machines/pricing.blade.php rename to resources/views/admin/machines/pricing.blade.php index 9fb71aa..fb35c44 100644 --- a/resources/views/admin/basic-settings/machines/pricing.blade.php +++ b/resources/views/admin/machines/pricing.blade.php @@ -2,11 +2,11 @@ @section('content')
+ x-data="machinePricing({{ Js::from($items) }}, '{{ route('admin.machines.pricing.update', $machine) }}')"> {{-- Header --}}
- @@ -14,15 +14,15 @@

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

- {{ $machine->serial_no }} + {{ $machine->serial_no }} {{ $machine->name }}
@@ -36,7 +36,7 @@ {{ __('Leave blank to use the global product price. Member price will be capped at the machine selling price.') }}

- + @@ -91,22 +91,36 @@ {{-- Machine Price --}} -
- $ - +
+ +
+ +
+
{{-- Member Price --}} -
- $ - +
+ +
+ +
+
@@ -123,12 +137,12 @@
- {{ __('Cancel') }}