star-cloud/resources/views/admin/sales/pharmacy-pickup/index.blade.php
terrylee 8977e7eb0f feat(pharmacy-pickup): 領藥單模組(後台建單→QR→機台B660掃碼→出貨回報)
- 機台系統設定:取物單模式(pickup_sheet)下新增「領藥單」開關 pharmacy_pickup_enabled,B014 下發 FunctionSet.PharmacyPickup
- 權限:銷售管理→領藥單 menu.sales.pharmacy-pickup(角色/帳號可勾選授權,租戶模板預設關)
- 資料模型:orders 加 order_type/pricing_slip_no/created_by(領藥單序號=flow_id)、pickup_codes.slot_no 可空
- 後台:PharmacyPickupController + PharmacyPickupService(以商品為單位建單、庫存不預扣)+ 建單/列表/列印頁(QR內嵌,不顯姓名)
- B660:領藥單回多貨道 items[](保留 slot_no 向後相容)+ 模式 gate + 標記已領
- 出貨回報:finalizePharmacyDispense(獨立於銷售/發票/閉環)+ flow_id 終態冪等 + 庫存只扣一次
- 修正 PickupCode::isValid() 對 null expires_at 的 NPE、status 補入 fillable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:54:04 +08:00

182 lines
12 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') }}</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">{{ $p->available }}</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