star-cloud/resources/views/admin/sales/pharmacy-pickup/index.blade.php
root 003ba9b1ad [FEAT] 新增領藥單(Pharmacy Pickup / Rx)功能並強化出貨防呆
1. 領藥單後台:新增 PharmacyPickupController 與 PharmacyPickupService,提供領藥單列表/建立/作廢/列印 QR;routes/web.php 新增受 menu.sales.pharmacy-pickup 權限控管的路由群組,RoleSeeder 補該權限、sidebar 新增選單。
2. 訂單模型:Order 新增 order_type(sale/pharmacy_pickup)、pricing_slip_no、created_by 欄位與 AWAITING_PICKUP 狀態,新增 sales/pharmacyPickup scope 及 creator/pickupCode 關聯。
3. 機台設定:系統設定新增「領藥單」開關,僅在取物單(pickup_sheet)模式下可啟用、其他模式強制關閉;含後端防呆、toggle UI 與啟用功能標籤顯示。
4. 機台 API:getSettings FunctionSet 增加 PharmacyPickup 旗標;B660 取貨核銷擴充領藥單多貨道出貨清單回傳,庫存不足/無可用貨道時嚴格擋關且不消耗領藥碼。
5. 出貨流程:TransactionService finalizeTransaction 以 order_no(RX) 對應預建領藥單,走 finalizePharmacyDispense 獨立冪等出貨流程,並標記領藥碼已領與核銷日誌。
6. 防呆修正:recordDispense 庫存<=0 時不再扣減以避免負庫存;syncSlots 上報清單為空時不執行全量刪除,避免誤清貨道。
7. 資料庫:新增 2 個 migration(orders 增領藥單欄位、pickup_codes 之 slot_no 改 nullable);PickupCode 開放 status 可填、isValid 容許 null expires_at。
8. 多語系/文件:zh_TW/en/ja 三語系對齊補齊領藥單相關詞彙(27 個);config/api-docs.php 補 PharmacyPickup 旗標說明。
2026-06-23 02:19:17 +00:00

187 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.admin')
@section('content')
@php
$statusLabels = [
'awaiting_pickup' => __('Awaiting Pickup'),
'completed' => __('Picked Up'),
'failed' => __('Voided'),
'pending' => __('Pending'),
'abandoned' => __('Abandoned'),
];
$statusColors = [
'awaiting_pickup' => 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400',
'completed' => 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400',
'failed' => 'bg-rose-100 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400',
];
@endphp
<div class="space-y-6 pb-20">
<div class="flex items-center gap-3">
<h1 class="text-2xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('menu.sales.pharmacy-pickup') }}</h1>
<span class="text-sm text-slate-400">{{ __('Issue a pharmacy pickup order, print the QR ticket, patient scans at the machine to dispense.') }}</span>
</div>
@if(session('success'))
<div class="px-4 py-3 rounded-xl bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20 text-sm font-bold">{{ session('success') }}</div>
@endif
@if(session('error'))
<div class="px-4 py-3 rounded-xl bg-rose-50 text-rose-700 border border-rose-200 dark:bg-rose-500/10 dark:text-rose-400 dark:border-rose-500/20 text-sm font-bold">{{ session('error') }}</div>
@endif
@if($errors->any())
<div class="px-4 py-3 rounded-xl bg-rose-50 text-rose-700 border border-rose-200 dark:bg-rose-500/10 dark:text-rose-400 dark:border-rose-500/20 text-sm">
<ul class="list-disc ps-5 space-y-1">
@foreach($errors->all() as $err)<li>{{ $err }}</li>@endforeach
</ul>
</div>
@endif
{{-- 建立領藥單 --}}
<div class="bg-white dark:bg-slate-800/60 rounded-2xl border border-slate-200 dark:border-slate-700/50 p-6 space-y-5">
<h2 class="text-lg font-black text-slate-700 dark:text-slate-200">{{ __('Create Pharmacy Pickup Order') }}</h2>
@if($machines->isEmpty())
<p class="text-sm text-amber-600 dark:text-amber-400">{{ __('No machines enabled for pharmacy pickup. Enable it under Machine System Settings (Pickup Sheet mode → Pharmacy Pickup switch).') }}</p>
@else
{{-- 選機台(變更即重新載入該機台可領藥品) --}}
<form method="GET" action="{{ route('admin.sales.pharmacy-pickup') }}" class="flex flex-wrap items-end gap-3">
<div class="flex flex-col gap-1">
<label class="text-xs font-black text-slate-400 uppercase tracking-widest">{{ __('Select Machine') }}</label>
<select name="machine_id" onchange="this.form.submit()"
class="luxury-input min-w-[260px] rounded-xl border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 py-2.5 px-3 text-sm">
<option value="">{{ __('-- Select Machine --') }}</option>
@foreach($machines as $m)
<option value="{{ $m->id }}" @selected($selectedMachine && $selectedMachine->id === $m->id)>{{ $m->name }} ({{ $m->serial_no }})</option>
@endforeach
</select>
</div>
</form>
@if($selectedMachine)
<form method="POST" action="{{ route('admin.sales.pharmacy-pickup.store') }}" class="space-y-4">
@csrf
<input type="hidden" name="machine_id" value="{{ $selectedMachine->id }}">
<div class="flex flex-col gap-1 max-w-xs">
<label class="text-xs font-black text-slate-400 uppercase tracking-widest">{{ __('Pricing Slip No.') }} <span class="text-slate-300 normal-case">({{ __('optional') }})</span></label>
<input type="text" name="pricing_slip_no" value="{{ old('pricing_slip_no') }}" maxlength="64"
class="luxury-input rounded-xl border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 py-2.5 px-3 text-sm"
placeholder="{{ __('e.g. the hospital pricing slip number') }}">
</div>
@if($products->isEmpty())
<p class="text-sm text-slate-500">{{ __('This machine currently has no medicine in stock.') }}</p>
@else
<div class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700/50">
<table class="w-full text-sm">
<thead class="bg-slate-50 dark:bg-slate-900/40 text-slate-500 dark:text-slate-400">
<tr>
<th class="py-2.5 px-3 text-left w-10"></th>
<th class="py-2.5 px-3 text-left">{{ __('Medicine') }}</th>
<th class="py-2.5 px-3 text-left">{{ __('Spec') }}</th>
<th class="py-2.5 px-3 text-center">{{ __('Available') }}<br><span class="text-[10px] font-normal text-slate-400">{{ __('(deducted by issued pickup orders)') }}</span></th>
<th class="py-2.5 px-3 text-center w-32">{{ __('Quantity') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
@foreach($products as $p)
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/40">
<td class="py-2 px-3 text-center">
<input type="checkbox" name="products[]" value="{{ $p->product_id }}"
class="w-4 h-4 text-cyan-500 rounded border-slate-300 dark:border-slate-600">
</td>
<td class="py-2 px-3 font-bold text-slate-700 dark:text-slate-200">{{ $p->name }}</td>
<td class="py-2 px-3 text-slate-500">{{ $p->spec ?: '-' }}</td>
<td class="py-2 px-3 text-center text-slate-600 dark:text-slate-300">
<span class="font-bold">{{ $p->available }}</span>
@if(($p->reserved ?? 0) > 0)
<span class="text-[11px] text-slate-400">{{ __('physical') }} {{ $p->physical }} {{ __('reserved') }} {{ $p->reserved }}</span>
@endif
</td>
<td class="py-2 px-3 text-center">
<input type="number" name="qty[{{ $p->product_id }}]" value="1" min="1" max="{{ $p->available }}"
class="w-20 text-center rounded-lg border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 py-1.5 px-2 text-sm">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<button type="submit" class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-cyan-500 hover:bg-cyan-600 text-white font-bold text-sm transition-colors">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/></svg>
{{ __('Create & Generate QR') }}
</button>
@endif
</form>
@endif
@endif
</div>
{{-- 領藥單列表 --}}
<div class="bg-white dark:bg-slate-800/60 rounded-2xl border border-slate-200 dark:border-slate-700/50 p-6 space-y-4">
<h2 class="text-lg font-black text-slate-700 dark:text-slate-200">{{ __('Pharmacy Pickup Orders') }}</h2>
<div class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700/50">
<table class="w-full text-sm">
<thead class="bg-slate-50 dark:bg-slate-900/40 text-slate-500 dark:text-slate-400">
<tr>
<th class="py-2.5 px-3 text-left">{{ __('Order No.') }}</th>
<th class="py-2.5 px-3 text-left">{{ __('Pricing Slip No.') }}</th>
<th class="py-2.5 px-3 text-left">{{ __('Machine') }}</th>
<th class="py-2.5 px-3 text-left">{{ __('Items') }}</th>
<th class="py-2.5 px-3 text-center">{{ __('Pickup Code') }}</th>
<th class="py-2.5 px-3 text-center">{{ __('Status') }}</th>
<th class="py-2.5 px-3 text-left">{{ __('Created') }}</th>
<th class="py-2.5 px-3 text-center">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
@forelse($orders as $order)
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/40 {{ session('created_order_id') == $order->id ? 'bg-emerald-50 dark:bg-emerald-500/5' : '' }}">
<td class="py-2.5 px-3 font-mono font-bold text-slate-700 dark:text-slate-200">{{ $order->order_no }}</td>
<td class="py-2.5 px-3 text-slate-500">{{ $order->pricing_slip_no ?: '-' }}</td>
<td class="py-2.5 px-3 text-slate-600 dark:text-slate-300">{{ $order->machine?->name ?? '-' }}</td>
<td class="py-2.5 px-3 text-slate-600 dark:text-slate-300">
@foreach($order->items as $it)
<div>{{ $it->product_name }} × {{ $it->quantity }}</div>
@endforeach
</td>
<td class="py-2.5 px-3 text-center font-mono font-bold text-cyan-600 dark:text-cyan-400">{{ $order->pickupCode?->code ?? '-' }}</td>
<td class="py-2.5 px-3 text-center">
<span class="inline-block px-2.5 py-1 rounded-full text-xs font-bold {{ $statusColors[$order->status] ?? 'bg-slate-100 text-slate-600' }}">{{ $statusLabels[$order->status] ?? $order->status }}</span>
</td>
<td class="py-2.5 px-3 text-slate-500 text-xs">
<div>{{ $order->created_at?->format('Y-m-d H:i') }}</div>
<div class="text-slate-400">{{ $order->creator?->name }}</div>
</td>
<td class="py-2.5 px-3 text-center">
<div class="flex items-center justify-center gap-3">
@if($order->status !== 'failed' && $order->pickupCode)
<a href="{{ route('admin.sales.pharmacy-pickup.print', $order) }}" target="_blank" class="text-xs font-bold text-cyan-600 hover:text-cyan-700">{{ __('Print') }}</a>
@endif
@if($order->status === 'awaiting_pickup')
<form method="POST" action="{{ route('admin.sales.pharmacy-pickup.cancel', $order) }}" onsubmit="return confirm('{{ __('Cancel this pharmacy pickup order?') }}')">
@csrf
<button type="submit" class="text-xs font-bold text-rose-500 hover:text-rose-600">{{ __('Cancel') }}</button>
</form>
@endif
@if($order->status === 'failed' && !($order->pickupCode))
<span class="text-slate-300">-</span>
@endif
</div>
</td>
</tr>
@empty
<tr><td colspan="8" class="py-8 text-center text-slate-400">{{ __('No pharmacy pickup orders yet.') }}</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $orders->links() }}</div>
</div>
</div>
@endsection