diff --git a/app/Http/Controllers/Admin/WarehouseController.php b/app/Http/Controllers/Admin/WarehouseController.php index 72aaa2f..0d78158 100644 --- a/app/Http/Controllers/Admin/WarehouseController.php +++ b/app/Http/Controllers/Admin/WarehouseController.php @@ -594,7 +594,7 @@ class WarehouseController extends Controller public function machineSlots(Machine $machine) { $slots = $machine->slots() - ->with(['product:id,name,image_url,barcode']) + ->with(['product:id,name,image_url,barcode,price']) ->orderByRaw('CAST(slot_no AS UNSIGNED) ASC') ->get(); @@ -703,16 +703,9 @@ class WarehouseController extends Controller ReplenishmentOrderItem::insert($itemsData); }); - if ($request->ajax()) { - session()->flash('success', __('Replenishment order created successfully')); - return response()->json([ - 'success' => true, - 'message' => __('Replenishment order created successfully') - ]); - } + session()->flash('success', __('Replenishment order created successfully')); - return redirect()->route('admin.warehouses.replenishments') - ->with('success', __('Replenishment order created successfully')); + return redirect()->route('admin.warehouses.replenishments'); } /** @@ -865,14 +858,18 @@ class WarehouseController extends Controller // pending → prepared:扣減倉庫庫存 if ($newStatus === ReplenishmentOrder::STATUS_PREPARED) { foreach ($replenishmentOrder->items as $item) { - $stock = WarehouseStock::where('warehouse_id', $replenishmentOrder->warehouse_id) - ->where('product_id', $item->product_id) - ->lockForUpdate() - ->first(); + $stock = WarehouseStock::firstOrCreate( + [ + 'warehouse_id' => $replenishmentOrder->warehouse_id, + 'product_id' => $item->product_id, + ], + [ + 'company_id' => $replenishmentOrder->company_id, + 'quantity' => 0, + ] + ); - if (!$stock || $stock->quantity < $item->quantity) { - throw new \Exception(__('Warehouse stock insufficient') . " ({$item->product?->name})"); - } + $stock->lockForUpdate(); $before = $stock->quantity; $stock->decrement('quantity', $item->quantity); @@ -914,6 +911,14 @@ class WarehouseController extends Controller 'completed' => __('Confirm Complete'), ]; + $statusMessages = [ + 'prepared' => __('Order prepared successfully, stock deducted'), + 'delivering' => __('Order is now in delivery'), + 'completed' => __('Order completed and stock updated'), + ]; + + session()->flash('success', $statusMessages[$newStatus] ?? __('Status updated successfully')); + return response()->json([ 'success' => true, 'message' => __('Status updated successfully') @@ -938,29 +943,35 @@ class WarehouseController extends Controller $replenishmentOrder->loadMissing('items'); foreach ($replenishmentOrder->items as $item) { - $stock = WarehouseStock::where('warehouse_id', $replenishmentOrder->warehouse_id) - ->where('product_id', $item->product_id) - ->lockForUpdate() - ->first(); - - if ($stock) { - $before = $stock->quantity; - $stock->increment('quantity', $item->quantity); - - StockMovement::create([ - 'company_id' => $replenishmentOrder->company_id, + $stock = WarehouseStock::firstOrCreate( + [ 'warehouse_id' => $replenishmentOrder->warehouse_id, 'product_id' => $item->product_id, - 'type' => 'in', - 'quantity' => $item->quantity, - 'before_qty' => $before, - 'after_qty' => $before + $item->quantity, - 'reference_type' => ReplenishmentOrder::class, - 'reference_id' => $replenishmentOrder->id, - 'note' => __('Replenishment cancelled, stock returned') . ' #' . $replenishmentOrder->order_no, - 'created_by' => Auth::id(), - ]); - } + ], + [ + 'company_id' => $replenishmentOrder->company_id, + 'quantity' => 0, + ] + ); + + $stock->lockForUpdate(); + + $before = $stock->quantity; + $stock->increment('quantity', $item->quantity); + + StockMovement::create([ + 'company_id' => $replenishmentOrder->company_id, + 'warehouse_id' => $replenishmentOrder->warehouse_id, + 'product_id' => $item->product_id, + 'type' => 'in', + 'quantity' => $item->quantity, + 'before_qty' => $before, + 'after_qty' => $before + $item->quantity, + 'reference_type' => ReplenishmentOrder::class, + 'reference_id' => $replenishmentOrder->id, + 'note' => __('Replenishment cancelled, stock returned') . ' #' . $replenishmentOrder->order_no, + 'created_by' => Auth::id(), + ]); } } @@ -969,10 +980,16 @@ class WarehouseController extends Controller ]); }); + $msg = __('Order has been cancelled'); + if (in_array($replenishmentOrder->getOriginal('status'), [ReplenishmentOrder::STATUS_PREPARED, ReplenishmentOrder::STATUS_DELIVERING])) { + $msg .= ' — ' . __('Stock returned to warehouse'); + } + + session()->flash('success', $msg); + return response()->json([ 'success' => true, - 'message' => __('Order has been cancelled'), - 'stock_returned' => in_array($replenishmentOrder->getOriginal('status'), [ReplenishmentOrder::STATUS_PREPARED, ReplenishmentOrder::STATUS_DELIVERING]), + 'message' => $msg ]); } @@ -999,10 +1016,11 @@ class WarehouseController extends Controller $replenishmentOrder->update(['assigned_to' => $user->id]); + session()->flash('success', __('Personnel assigned successfully')); + return response()->json([ 'success' => true, - 'message' => __('Personnel assigned successfully'), - 'assignee_name' => $user->name, + 'message' => __('Personnel assigned successfully') ]); } @@ -1101,16 +1119,23 @@ class WarehouseController extends Controller if ($warehouseId) { // Warehouse uses TenantScoped, findOrFail will respect it $warehouse = Warehouse::findOrFail($warehouseId); - $stocks = WarehouseStock::with(['product' => fn($q) => $q->select('id', 'name', 'image_url', 'name_dictionary_key')->with('translations')]) - ->where('warehouse_id', $warehouse->id) - ->where('quantity', '>', 0) + + // 獲取公司所有啟動中的商品 + $products = Product::where('company_id', $warehouse->company_id) + ->active() + ->select('id', 'name', 'image_url', 'name_dictionary_key') + ->with('translations') ->get(); + + // 獲取目前的庫存量(用於顯示,不限制選擇) + $stocks = WarehouseStock::where('warehouse_id', $warehouse->id) + ->pluck('quantity', 'product_id'); - $data = $stocks->map(fn($s) => [ - 'id' => $s->product_id, - 'name' => $s->product?->localized_name ?? 'Unknown', - 'quantity' => $s->quantity, - 'image' => $s->product?->image_url + $data = $products->map(fn($p) => [ + 'id' => $p->id, + 'name' => $p->localized_name ?? 'Unknown', + 'quantity' => $stocks[$p->id] ?? 0, + 'image' => $p->image_url ]); } elseif ($machineId) { // Machine uses TenantScoped and machine_access scope diff --git a/lang/ja.json b/lang/ja.json index 3680597..2074430 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -46,6 +46,7 @@ "Confirm Transfer Order": "転送伝票を確認", "Create and manage replenishment orders from warehouse to machines": "倉庫から機台への補充伝票の作成と管理", "Create Replenishment Order": "補充伝票を作成", + "Create stock replenishment for specific machines": "指定マシンの在庫補充伝票を作成", "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "倉庫間の転送および機台からの返品伝票を作成", "Create stock transfers between warehouses and machine returns": "倉庫間の転送および機台からの返品伝票を作成", "Create Transfer Order": "転送伝票を作成", @@ -88,9 +89,11 @@ "Loading...": "読み込み中...", "Low Stock": "在庫不足", "Low Stock Alerts": "低在庫アラート", + "Machine": "マシン", "Machine Distribution": "機器分布", "Machine Distribution Map": "機器分布マップ", "Machine Inventory Overview": "機台在庫概要", + "Machine Stock": "マシン在庫", "Machine Replenishment": "機台補充", "Machine Return": "機台返品", "Machine to Warehouse": "機台から倉庫", @@ -131,6 +134,7 @@ "Please enter configuration name": "設定名を入力してください", "Please select a company": "会社を選択してください", "Please select warehouse and machine": "倉庫とマシンを選択してください", + "Please Select Slot first": "先にスロットを選択してください", "Points": "ポイント", "Prepared": "準備済み", "Preparing": "準備中", @@ -147,6 +151,7 @@ "Quick Replenish": "クイック補充", "Real-time slot-level inventory across all machines": "すべての機台におけるリアルタイムの貨道別在庫", "Replenishment cancelled, stock returned": "補充キャンセル、在庫返却", + "Replenishment Details": "機台補充伝票詳細", "Replenishment Items": "補充項目", "Replenishment order confirmed and inventory updated": "補充伝票が確定され、在庫が更新されました", "Replenishment order created successfully": "補充伝票が正常に作成されました", @@ -242,5 +247,10 @@ "Welcome Gift Feature": "来店特典機能", "Stock / Capacity": "在庫 / 上限", "Warehouse Stock": "倉庫在庫", - "Recalculate": "再計算" + "Recalculate": "再計算", + "Work Content": "作業内容", + "No content provided": "内容は提供されていません", + "Maintenance Photos": "メンテナンス写真", + "Execution Time": "実行時間", + "Product Name": "商品名" } \ No newline at end of file diff --git a/lang/zh_TW.json b/lang/zh_TW.json index 4fdadf3..5464362 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -1,4 +1,6 @@ { + "Start Time": "開始時間", + "End Time": "結束時間", "15 Seconds": "15 秒", "30 Seconds": "30 秒", "60 Seconds": "60 秒", @@ -258,6 +260,9 @@ "Confirm this transfer?": "確定要確認此調撥作業嗎?", "Confirm Transfer": "確認調撥", "Confirm Transfer Order": "確認調撥單", + "Are you sure you want to confirm preparation? This will deduct stock from the warehouse.": "確定要確認備貨嗎?此操作將會扣除倉庫庫存。", + "Are you sure you want to start delivery?": "確定要開始配送嗎?", + "Are you sure you want to confirm completion? This will update the machine stock.": "確定要確認完成嗎?此操作將會更新機台貨道庫存。", "Connected": "通訊中", "Connecting...": "連線中", "Connection lost (LWT)": "連線中斷", @@ -292,6 +297,7 @@ "Create Product": "建立商品", "Create Replenishment Order": "建立補貨單", "Create replenishment orders and manage restocking from warehouse to machines": "建立補貨單,管理從倉庫到機台的補貨流程", + "Create stock replenishment for specific machines": "為指定機台建立庫存補貨單", "Create Role": "建立角色", "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "建立倉庫間調撥或機台退庫單", "Create stock transfers between warehouses and machine returns": "建立倉庫間調撥或機台退庫單", @@ -624,6 +630,7 @@ "Low stock and out-of-stock alerts": "低庫存與缺貨警示", "Loyalty & Features": "行銷與點數", "Machine Advertisement Settings": "機台廣告設置", + "Machine": "機台", "Machine Count": "機台數量", "Machine created successfully.": "機台已成功建立。", "Machine Details": "機台詳情", @@ -634,6 +641,7 @@ "Machine Info": "機台資訊", "Machine Information": "機台資訊", "Machine Inventory": "機台庫存", + "Machine Stock": "機台庫存", "Machine Inventory Overview": "機台庫存概覽", "Machine is heartbeat normal": "機台通訊正常", "Machine List": "機台列表", @@ -887,10 +895,13 @@ "Optional remarks...": "選填備註...", "Order already completed": "訂單已完成", "Order already processed": "訂單已處理", + "Order completed and stock updated": "訂單已完成,機台庫存已更新", "Order Details": "進貨單詳情", "Order has been cancelled": "訂單已取消", "Order Info": "單據資訊", + "Order is now in delivery": "訂單配送中", "Order Management": "訂單管理", + "Order prepared successfully, stock deducted": "訂單已成功備貨,庫存已扣除", "Order No.": "單號", "Orders": "購買單", "Original": "原始", @@ -977,6 +988,7 @@ "Please select a material": "請選擇素材", "Please select a slot": "請選擇貨道", "Please select warehouse and machine": "請選擇倉庫和機台", + "Please Select Slot first": "請先選擇貨道", "PNG, JPG up to 2MB": "支援 PNG, JPG (最大 2MB)", "PNG, JPG, WEBP up to 10MB": "支援 PNG, JPG, WEBP 格式,且不超過 10MB", "Point Rules": "點數規則", @@ -1071,6 +1083,7 @@ "Repair": "維修", "Replenishment": "補貨", "Replenishment Audit": "補貨單", + "Replenishment Details": "補貨單詳情", "Replenishment cancelled, stock returned": "補貨取消,庫存退回", "Replenishment completed": "補貨完成", "Replenishment history": "補貨歷史紀錄", @@ -1508,5 +1521,7 @@ "待填寫": "待填寫", "Stock / Capacity": "庫存/上限", "Warehouse Stock": "倉庫數量", - "Recalculate": "重新計算" + "Recalculate": "重新計算", + "Work Content": "作業內容", + "Product Name": "商品名稱" } \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index 88379b5..e2f4876 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -172,7 +172,7 @@ @layer components { .luxury-nav-item { - @apply flex items-center gap-x-3.5 py-2.5 px-4 text-sm font-semibold rounded-xl transition-all duration-200; + @apply flex items-center gap-x-3.5 py-2.5 px-4 text-sm font-semibold rounded-xl transition-all duration-200 overflow-hidden; @apply text-slate-600 hover:text-slate-900 hover:bg-slate-100; @apply dark:text-slate-300 dark:hover:text-white dark:hover:bg-white/5; } diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 4059996..a347062 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -8,7 +8,8 @@
{{ __('Real-time status monitoring') }}
+ class="text-6xl sm:text-7xl font-black text-cyan-500 drop-shadow-[0_0_20px_rgba(6,182,212,0.3)] leading-none"> {{ $activeMachines }}
{{ __('Total Connected') }}
@@ -66,7 +71,8 @@{{ __('Monthly cumulative revenue overview') }}
{{ __("Today's Transactions") }}
+{{ __("Today Cumulative Sales") }}
${{ number_format($totalRevenue / 30, 0) }}
@@ -101,7 +107,8 @@{{ __('vs Yesterday') }}
+{{ + __('vs Yesterday') }}
{{ __('Real-time monitoring across all machines') }}
@@ -210,8 +210,8 @@ :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': isLoadingTable }"> -
+ {{ $machine->serial_no ?: '--' }} +
+{{ __('Temperature') }}
+{{ $machine->temperature ?? '--' }}°C
+{{ __('APP Version') }}
+{{ $machine->firmware_version ?: '-' }}
+{{ __('Last Page') }}
+{{ $machine->current_page_label ?: '-' }}
+{{ __('Status') }}
++ @if($cStatus === 'online') + {{ __('Online') }} + @elseif($cStatus === 'error') + {{ __('Error') }} + @else + {{ __('Offline') }} + @endif +
+{{ __('Last Heartbeat') }}
++ {{ $machine->last_heartbeat_at ? $machine->last_heartbeat_at->format('Y-m-d H:i:s') : '-' }} +
+{{ __('Manage machine access permissions') }}
|
+ class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 shadow-sm overflow-hidden shrink-0">
{{ __('No - accounts found') }} +{{ __('No accounts found') }} |
+ {{ $user->username }} +
+{{ __('No accounts found') }}
+{{ __('Track device health and maintenance history') }}
| {{ __('Time') }} | {{ __('Machine Information') }} | + @if(auth()->user()->isSystemAdmin()){{ __('Company') }} | + @endif{{ __('Category') }} | {{ __('Engineer') }} | {{ __('Actions') }} | @@ -143,12 +170,14 @@ {{ $record->machine->serial_no ?? 'N/A' }} + @if(auth()->user()->isSystemAdmin())
- {{ $record->company->name ?? 'N/A' }}
+ {{ $record->company->name ?? __('System') }}
{{ $record->company->company_code ?? '' }}
|
+ @endif
@php $color = match($record->category) { @@ -195,12 +224,102 @@ |
|---|
+ {{ $record->machine->serial_no ?? 'N/A' }} +
+{{ __('Execution Time') }}
+{{ $record->maintenance_at->format('Y-m-d H:i') }}
+{{ __('Engineer') }}
+{{ __('Company') }}
++ {{ $record->company->name ?? __('System') }} ({{ $record->company->company_code ?? '' }}) +
+{{ __('No maintenance records found') }}
+{{ - __('Loading Data') }}...
-{{ __('Manage your warehouses, including main and branch warehouses') }}
+ {{ $warehouse->address ?: __('No address provided') }} +
+{{ __('Products') }}
+{{ $warehouse->products_count ?? 0 }}
+{{ __('Stock') }}
+{{ (int)($warehouse->total_stock ?? 0) }}
+{{ __('Status') }}
+ @if($warehouse->is_active) +{{ __('Company') }}
++ {{ $warehouse->company->name ?? __('System') }} +
+{{ __('No warehouses found') }}
+{{ + __('Loading Data') }}...
+{{ __('Real-time slot-level inventory across all machines') }}
+{{ __('Real-time slot-level inventory across all machines') }}
| {{ __('Machine') }} | -{{ __('Serial No.') }} | -{{ __('Slots') }} | -{{ __('Total Stock') }} | -{{ __('Stock Rate') }} | -{{ __('Actions') }} | ++ {{ __('Machine') }} | ++ {{ __('Serial No.') }} | ++ {{ __('Slots') }} | ++ {{ __('Total Stock') }} | ++ {{ __('Stock Rate') }} | ++ {{ __('Actions') }} |
|---|---|---|---|---|---|---|---|---|---|---|---|
|
-
+
@if($machine->image_urls && isset($machine->image_urls[0]))
-
- {{ $machine->name }}
+ {{
+ $machine->name }}
|
- {{ $machine->serial_no }} + {{ + $machine->serial_no }} | - {{ $machine->total_slots ?? 0 }} + {{ $machine->total_slots ?? 0 + }} | {{ $currentStock }} |
-
+
-
-
- {{ $fillRate }}%
-
+
+
+ {{ $fillRate
+ }}%
+
|
-
|
||||||
| {{ __('No machines found') }} | |||||||||||
| {{ __('No + machines found') }} | +|||||||||||
| {{ __('Slot') }} | -{{ __('Product') }} | -{{ __('Barcode') }} | -{{ __('Stock Level') }} / {{ __('Max Stock') }} | -{{ __('Sale Price') }} | +{{ + __('Slot') }} | +{{ + __('Product') }} | +{{ + __('Barcode') }} | ++ {{ __('Stock Level') }} / {{ __('Max Stock') }} | ++ {{ __('Sale Price') }} |
|---|---|---|---|---|---|---|---|---|---|
| - + |
-
+
-
-
+
+
+
|
- + |
-
+
/
|
- + | - - / - + |
+
+
+ /
+
+
|
@@ -146,10 +148,10 @@
{{-- 庫存 / 上限 --}}
{{ __('Stock / Capacity') }}
-
-
- /
-
+
diff --git a/resources/views/admin/warehouses/partials/modal-replenishment-create.blade.php b/resources/views/admin/warehouses/partials/modal-replenishment-create.blade.php
index 9a9b64e..55576c6 100644
--- a/resources/views/admin/warehouses/partials/modal-replenishment-create.blade.php
+++ b/resources/views/admin/warehouses/partials/modal-replenishment-create.blade.php
@@ -23,7 +23,7 @@
+
+ /
+
-
@@ -39,18 +39,17 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
-
+
{{-- Header --}}
-
-
- {{ __('Replenishment Details') }}-
+
{{-- Content --}}
@@ -35,8 +30,8 @@
{{-- Info Grid --}}
+
-
- {{ __('Replenishment Details') }}++ + + +
-
-
-
+
-
-
-
-
{{ __('Status') }} - {{ __('Status') }} +
-
{{ __('Assigned To') }} - +{{ __('Assigned To') }} +
-
{{ __('Created By') }} - +{{ __('Created By') }} +
-
{{ __('Created At') }} - +{{ __('Created At') }} +
-
{{ __('Completed At') }} - +{{ __('Completed At') }} +
-
{{ __('Note') }} - +{{ __('Note') }} +{{ __('Replenishment Items') }}- +
@@ -108,7 +103,7 @@
- + {{ __('Stock') }}: / diff --git a/resources/views/admin/warehouses/partials/table-replenishments.blade.php b/resources/views/admin/warehouses/partials/table-replenishments.blade.php index 6c1f048..c919d27 100644 --- a/resources/views/admin/warehouses/partials/table-replenishments.blade.php +++ b/resources/views/admin/warehouses/partials/table-replenishments.blade.php @@ -17,10 +17,21 @@ @forelse($orders as $order)
- | id }}')">
- {{ $order->order_no }}
- {{ $order->created_at->format('Y-m-d H:i:s') }}
+ id }}')">
+ |
{{-- 機台 --}}
@@ -54,8 +65,8 @@
+
+
+
+
+ {{ $order->order_no }}
+
+
+ {{ $order->created_at->format('Y-m-d H:i:s') }}
+
+
|
{{-- 查看詳情 --}}
- ${s.stock || 0}/${s.max_stock || 0} `;
+ opt.setAttribute('data-description', stockHtml);
+
+ if (item.slot_no == s.slot_no) opt.selected = true;
selectEl.appendChild(opt);
});
+
selectEl.setAttribute('data-hs-select', JSON.stringify({
- "placeholder": "{{ __('Select Product') }}",
- "toggleClasses": "hs-select-toggle luxury-select-toggle w-full text-left",
- "toggleTemplate": "",
+ "hasSearch": false,
"strategy": "fixed"
}));
+
wrapper.appendChild(selectEl);
- selectEl.addEventListener('change', (e) => { item.product_id = e.target.value; });
+ selectEl.addEventListener('change', (e) => {
+ item.slot_no = e.target.value;
+ // Auto-select product based on slot
+ const slotData = this.machineSlots.find(s => s.slot_no == item.slot_no);
+ if (slotData) {
+ item.product_id = slotData.product_id;
+ item.current_stock = slotData.stock || 0;
+ item.max_stock = slotData.max_stock || 0;
+ // Update the product display for this row
+ this.updateProductDisplay(idx);
+ }
+ });
if (window.HSStaticMethods) window.HSStaticMethods.autoInit('select');
});
},
+ updateProductDisplay(index = null) {
+ const indices = index !== null ? [index] : this.items.map((_, i) => i);
+
+ indices.forEach(idx => {
+ const item = this.items[idx];
+ const wrapper = document.getElementById(`product-select-wrapper-${idx}`);
+ if (!wrapper) return;
+
+ wrapper.innerHTML = '';
+
+ if (!item.product_id) {
+ wrapper.innerHTML = `
+
+ {{ __('Please Select Slot first') }}
+
+ `;
+ return;
+ }
+
+ const productInWarehouse = this.availableProducts.find(p => p.id == item.product_id);
+ const productInfo = this.allProducts.find(p => p.id == item.product_id);
+ const productName = productInfo ? productInfo.name : (productInWarehouse ? productInWarehouse.name : 'Unknown');
+
+ // Priority: stock from selected warehouse, otherwise N/A or 0
+ const stock = productInWarehouse ? productInWarehouse.quantity : 0;
+ const stockText = this.fromId ? `{{ __('Stock') }}: ${stock}` : '{{ __('Select Warehouse') }}';
+
+ wrapper.innerHTML = `
+
+
+ `;
+ });
+ },
+
async submitReplenishment() {
const form = document.getElementById('replenishmentForm');
const warehouseId = this.fromId;
@@ -172,23 +287,7 @@
}
this.loading = true;
- try {
- const formData = new FormData(form);
- const response = await fetch(form.action, {
- method: 'POST', body: formData,
- headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' }
- });
- const result = await response.json();
- if (response.ok && result.success) {
- this.showReplenishmentModal = false;
- window.showToast?.(result.message, 'success');
- this.fetchTabData();
- } else if (response.status === 422) {
- const firstError = Object.values(result.errors || {})[0]?.[0] || '{{ __("Validation failed") }}';
- window.showToast?.(firstError, 'error');
- } else { window.showToast?.(result.message || '{{ __("System error") }}', 'error'); }
- } catch (e) { console.error(e); window.showToast?.('{{ __("System Error") }}', 'error'); }
- finally { this.loading = false; }
+ form.submit();
},
// 一鍵補貨預覽
@@ -227,7 +326,7 @@
available -= this.autoSlots[i].quantity;
}
}
- return Math.max(0, Math.min(slot.quantity, available));
+ return available;
},
removeAutoSlot(idx) {
@@ -262,10 +361,7 @@
const data = await res.json();
if (data.success) {
this.showAutoModal = false;
- this.autoPreviewLoaded = false;
- this.autoSlots = [];
- window.showToast?.(data.message, 'success');
- this.fetchTabData();
+ window.location.reload();
} else {
window.showToast?.(data.message || '{{ __("Creation failed") }}', 'error');
}
@@ -273,22 +369,54 @@
finally { this.autoLoading = false; }
},
- // 狀態推進
+ // 狀態推進 (顯示 Modal)
async advanceStatus(orderId, newStatus) {
- const labels = { prepared: '{{ __("Confirm Prepare") }}', delivering: '{{ __("Start Delivery") }}', completed: '{{ __("Confirm Complete") }}' };
- if (!confirm(labels[newStatus] + '?')) return;
+ this.statusTargetId = orderId;
+ this.statusTargetValue = newStatus;
+
+ const labels = {
+ prepared: '{{ __("Confirm Prepare") }}',
+ delivering: '{{ __("Start Delivery") }}',
+ completed: '{{ __("Confirm Complete") }}'
+ };
+ const messages = {
+ prepared: '{{ __("Are you sure you want to confirm preparation? This will deduct stock from the warehouse.") }}',
+ delivering: '{{ __("Are you sure you want to start delivery?") }}',
+ completed: '{{ __("Are you sure you want to confirm completion? This will update the machine stock.") }}'
+ };
+
+ this.statusModalTitle = labels[newStatus];
+ this.statusModalMessage = messages[newStatus];
+ this.showStatusModal = true;
+ },
+
+ // 執行狀態更新
+ async executeStatusUpdate() {
this.loading = true;
try {
- const res = await fetch(`{{ url('/admin/warehouses/replenishments') }}/${orderId}/status`, {
+ const res = await fetch(`{{ url('/admin/warehouses/replenishments') }}/${this.statusTargetId}/status`, {
method: 'PATCH',
- headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
- body: JSON.stringify({ status: newStatus })
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'application/json',
+ 'X-CSRF-TOKEN': '{{ csrf_token() }}'
+ },
+ body: JSON.stringify({ status: this.statusTargetValue })
});
- const data = await res.json();
- if (data.success) { window.showToast?.(data.message, 'success'); this.fetchTabData(); }
- else { window.showToast?.(data.message || '{{ __("Operation failed") }}', 'error'); }
- } catch (e) { console.error(e); window.showToast?.('{{ __("System Error") }}', 'error'); }
- finally { this.loading = false; }
+ if (res.ok) {
+ this.showStatusModal = false;
+ window.location.reload();
+ } else {
+ const data = await res.json();
+ window.showToast?.(data.message || '{{ __("Operation failed") }}', 'error');
+ }
+ } catch (e) {
+ console.error(e);
+ window.showToast?.('{{ __("System Error") }}', 'error');
+ } finally {
+ this.loading = false;
+ }
},
// 取消
@@ -300,14 +428,13 @@
method: 'PATCH',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' }
});
- const data = await res.json();
- if (data.success) {
+ if (res.ok) {
this.showCancelModal = false;
- let msg = data.message;
- if (data.stock_returned) msg += ' — {{ __("Stock returned to warehouse") }}';
- window.showToast?.(msg, 'success');
- this.fetchTabData();
- } else { window.showToast?.(data.message, 'error'); }
+ window.location.reload();
+ } else {
+ const data = await res.json();
+ window.showToast?.(data.message, 'error');
+ }
} catch (e) { console.error(e); window.showToast?.('{{ __("System Error") }}', 'error'); }
finally { this.loading = false; }
},
@@ -323,9 +450,13 @@
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
body: JSON.stringify({ assigned_to: this.assignUserId })
});
- const data = await res.json();
- if (data.success) { this.showAssignModal = false; window.showToast?.(data.message, 'success'); this.fetchTabData(); }
- else { window.showToast?.(data.message, 'error'); }
+ if (res.ok) {
+ this.showAssignModal = false;
+ window.location.reload();
+ } else {
+ const data = await res.json();
+ window.showToast?.(data.message, 'error');
+ }
} catch (e) { console.error(e); }
finally { this.loading = false; }
},
@@ -352,14 +483,14 @@
}
-
+
+
+ {{ __('Product Name') }}
+ ${productName}
+
+
+ {{ __('Machine Stock') }}
+
+
+ ${item.current_stock}
+ /
+ ${item.max_stock}
+
+
+ {{ __('Source') }}
+ ${stockText}
+
+
+
{{-- Header --}}
-
+
-
{{ __('Machine Replenishment') }}+{{ __('Machine Replenishment') }}{{ __('Create and manage replenishment orders from warehouse to machines') }}
@@ -391,14 +522,14 @@
{{-- Filters --}}
-
+
@endsection
diff --git a/resources/views/admin/warehouses/transfers.blade.php b/resources/views/admin/warehouses/transfers.blade.php
index 0c6f916..ca4cb7a 100644
--- a/resources/views/admin/warehouses/transfers.blade.php
+++ b/resources/views/admin/warehouses/transfers.blade.php
@@ -312,7 +312,7 @@
}
-
+
@@ -423,5 +556,6 @@
@include('admin.warehouses.partials.slideover-replenishment-details')
@include('admin.warehouses.partials.modal-replenishment-cancel')
@include('admin.warehouses.partials.modal-replenishment-assign')
+ @include('admin.warehouses.partials.modal-replenishment-status')
+
@include('admin.warehouses.partials.table-replenishments')
diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php
index f84a687..1b3a9db 100644
--- a/resources/views/layouts/admin.blade.php
+++ b/resources/views/layouts/admin.blade.php
@@ -24,7 +24,13 @@
@vite(['resources/css/app.css', 'resources/js/app.js'])
-
+
- |