[FEAT] 強化遠端指令中心的操作備註功能
1. 新增 remote_commands 資料表的 remark 欄位以儲存操作備註。 2. 更新 RemoteCommand 模型,將 note 與 remark 加入可批量賦值名單。 3. 更新 MachineService 的 dispatchDispense 函式,支援接收與記錄操作備註。 4. 更新 RemoteController 的 storeCommand 邏輯,將前端輸入的備註存入 remark 並支援遠端出貨指令。 5. 優化操作紀錄搜尋邏輯,支援搜尋備註欄位。 6. 重構指令中心操作紀錄介面,桌面版新增「操作備註」欄位,並優化手機版卡片顯示。 7. 調整表格欄位寬度與標頭樣式,解決「指令類型」寬度過大的問題。
This commit is contained in:
parent
ad921011f0
commit
426253b1c3
@ -53,7 +53,8 @@ class RemoteController extends Controller
|
|||||||
->orWhere('serial_no', 'like', "%{$search}%");
|
->orWhere('serial_no', 'like', "%{$search}%");
|
||||||
})->orWhereHas('user', function ($uq) use ($search) {
|
})->orWhereHas('user', function ($uq) use ($search) {
|
||||||
$uq->where('name', 'like', "%{$search}%");
|
$uq->where('name', 'like', "%{$search}%");
|
||||||
});
|
})->orWhere('remark', 'like', "%{$search}%")
|
||||||
|
->orWhere('note', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +146,8 @@ class RemoteController extends Controller
|
|||||||
$this->machineService->dispatchDispense(
|
$this->machineService->dispatchDispense(
|
||||||
Machine::findOrFail($validated['machine_id']),
|
Machine::findOrFail($validated['machine_id']),
|
||||||
$validated['slot_no'],
|
$validated['slot_no'],
|
||||||
auth()->id()
|
auth()->id(),
|
||||||
|
$validated['note'] ?? null
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$payload = [];
|
$payload = [];
|
||||||
@ -169,7 +171,7 @@ class RemoteController extends Controller
|
|||||||
'command_type' => $validated['command_type'],
|
'command_type' => $validated['command_type'],
|
||||||
'payload' => $payload,
|
'payload' => $payload,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
'note' => $validated['note'] ?? null,
|
'remark' => $validated['note'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 推播 MQTT 指令
|
// 推播 MQTT 指令
|
||||||
|
|||||||
@ -16,6 +16,8 @@ class RemoteCommand extends Model
|
|||||||
'payload',
|
'payload',
|
||||||
'status',
|
'status',
|
||||||
'ttl',
|
'ttl',
|
||||||
|
'note',
|
||||||
|
'remark',
|
||||||
'executed_at',
|
'executed_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -403,9 +403,9 @@ class MachineService
|
|||||||
/**
|
/**
|
||||||
* 遠端出貨指令下發 (指令模式,不再預扣庫存)
|
* 遠端出貨指令下發 (指令模式,不再預扣庫存)
|
||||||
*/
|
*/
|
||||||
public function dispatchDispense(Machine $machine, string $slotNo, ?int $userId = null): RemoteCommand
|
public function dispatchDispense(Machine $machine, string $slotNo, ?int $userId = null, ?string $remark = null): RemoteCommand
|
||||||
{
|
{
|
||||||
return DB::transaction(function () use ($machine, $slotNo, $userId) {
|
return DB::transaction(function () use ($machine, $slotNo, $userId, $remark) {
|
||||||
$slot = $machine->slots()->where('slot_no', $slotNo)->lockForUpdate()->firstOrFail();
|
$slot = $machine->slots()->where('slot_no', $slotNo)->lockForUpdate()->firstOrFail();
|
||||||
|
|
||||||
// 併行檢查:若有 pending 指令,超過 1 分鐘視為逾時可覆蓋
|
// 併行檢查:若有 pending 指令,超過 1 分鐘視為逾時可覆蓋
|
||||||
@ -440,6 +440,7 @@ class MachineService
|
|||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'command_type' => 'dispense',
|
'command_type' => 'dispense',
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
|
'remark' => $remark,
|
||||||
'payload' => [
|
'payload' => [
|
||||||
'slot_no' => (string)$slotNo,
|
'slot_no' => (string)$slotNo,
|
||||||
'current_stock' => $slot->stock,
|
'current_stock' => $slot->stock,
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('remote_commands', function (Blueprint $table) {
|
||||||
|
$table->string('remark')->nullable()->after('note')->comment('操作備註');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('remote_commands', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('remark');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -129,10 +129,13 @@
|
|||||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center whitespace-nowrap">
|
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center whitespace-nowrap">
|
||||||
{{ __('Picked up Time') }}</th>
|
{{ __('Picked up Time') }}</th>
|
||||||
<th
|
<th
|
||||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
|
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 whitespace-nowrap">
|
||||||
{{ __('Command Type') }}</th>
|
{{ __('Command Type') }}</th>
|
||||||
<th
|
<th
|
||||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
|
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 whitespace-nowrap">
|
||||||
|
{{ __('Operation Note') }}</th>
|
||||||
|
<th
|
||||||
|
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 whitespace-nowrap">
|
||||||
{{ __('Operator') }}</th>
|
{{ __('Operator') }}</th>
|
||||||
<th
|
<th
|
||||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
||||||
@ -190,22 +193,39 @@
|
|||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-6">
|
<td class="px-6 py-6">
|
||||||
<div class="flex flex-col min-w-[200px]">
|
<div class="flex flex-col min-w-[140px]">
|
||||||
<span
|
<span
|
||||||
class="text-sm font-black text-slate-700 dark:text-slate-300 tracking-tight"
|
class="text-sm font-black text-slate-700 dark:text-slate-300 tracking-tight whitespace-nowrap"
|
||||||
x-text="getCommandName({{ Js::from($item->command_type) }})"></span>
|
x-text="getCommandName({{ Js::from($item->command_type) }})"></span>
|
||||||
<div class="flex flex-col gap-0.5 mt-1">
|
<div class="flex flex-col gap-0.5 mt-1">
|
||||||
<span x-show="getPayloadDetails({{ Js::from($item) }})"
|
<span x-show="getPayloadDetails({{ Js::from($item) }})"
|
||||||
class="text-[11px] font-bold text-cyan-600 dark:text-cyan-400/80 bg-cyan-500/5 px-2 py-0.5 rounded-md border border-cyan-500/10 w-fit"
|
class="text-[11px] font-bold text-cyan-600 dark:text-cyan-400/80 bg-cyan-500/5 px-2 py-0.5 rounded-md border border-cyan-500/10 w-fit"
|
||||||
:class="{'line-through opacity-60': {{ Js::from($item->status) }} === 'failed'}"
|
:class="{'line-through opacity-60': {{ Js::from($item->status) }} === 'failed'}"
|
||||||
x-text="getPayloadDetails({{ Js::from($item) }})"></span>
|
x-text="getPayloadDetails({{ Js::from($item) }})"></span>
|
||||||
@if($item->note)
|
|
||||||
<span class="text-[10px] text-slate-400 italic pl-1"
|
|
||||||
x-text="translateNote({{ Js::from($item->note) }})"></span>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-6 py-6">
|
||||||
|
<div class="flex flex-col min-w-[200px]">
|
||||||
|
@if($item->remark)
|
||||||
|
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 break-words leading-tight">{{ $item->remark }}</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($item->note)
|
||||||
|
<div class="flex items-center gap-1 mt-1 opacity-60">
|
||||||
|
<svg class="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span class="text-[10px] text-slate-400 italic font-medium"
|
||||||
|
x-text="translateNote({{ Js::from($item->note) }})"></span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(!$item->remark && !$item->note)
|
||||||
|
<span class="text-slate-300 dark:text-slate-700">-</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-6 whitespace-nowrap">
|
<td class="px-6 py-6 whitespace-nowrap">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
@ -301,12 +321,27 @@
|
|||||||
class="text-[10px] font-bold text-cyan-600 dark:text-cyan-400/80 bg-cyan-500/5 px-2 py-0.5 rounded-md border border-cyan-500/10 w-fit"
|
class="text-[10px] font-bold text-cyan-600 dark:text-cyan-400/80 bg-cyan-500/5 px-2 py-0.5 rounded-md border border-cyan-500/10 w-fit"
|
||||||
:class="{'line-through opacity-60': {{ Js::from($item->status) }} === 'failed'}"
|
:class="{'line-through opacity-60': {{ Js::from($item->status) }} === 'failed'}"
|
||||||
x-text="getPayloadDetails({{ Js::from($item) }})"></span>
|
x-text="getPayloadDetails({{ Js::from($item) }})"></span>
|
||||||
@if($item->note)
|
|
||||||
<span class="text-[10px] text-slate-400 italic" x-text="translateNote({{ Js::from($item->note) }})"></span>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if($item->remark || $item->note)
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Operation Note') }}</p>
|
||||||
|
<div class="flex flex-col gap-1.5 p-3 rounded-2xl bg-slate-50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-800/50">
|
||||||
|
@if($item->remark)
|
||||||
|
<span class="text-sm font-bold text-slate-700 dark:text-slate-200 leading-tight">{{ $item->remark }}</span>
|
||||||
|
@endif
|
||||||
|
@if($item->note)
|
||||||
|
<div class="flex items-center gap-1.5 opacity-60">
|
||||||
|
<svg class="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span class="text-[10px] text-slate-400 italic font-medium" x-text="translateNote({{ Js::from($item->note) }})"></span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Creation Time') }}</p>
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Creation Time') }}</p>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user