Fix POS: АТОЛ is a fiscal register, not a payment method
- Remove АТОЛ from payment method selector (now only Наличные / Терминал) - Add fiscal receipt block below payment: shows АТОЛ connection status + toggle to print fiscal receipt - Remove atolRevenue/atolCount from shift totals and X-report - Add АТОЛ connection status line to X-report - Success overlay shows 'Фискальный чек распечатан' when АТОЛ connected and print enabled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ interface RoomBalance {
|
||||
nights: number
|
||||
}
|
||||
|
||||
type PaymentMethod = 'cash' | 'terminal' | 'atol'
|
||||
type PaymentMethod = 'cash' | 'terminal'
|
||||
type ChargePurpose = 'stay' | 'breakfast' | 'transfer' | 'parking' | 'extra'
|
||||
type PosTab = 'main' | 'report' | 'atol'
|
||||
type AtolConnection = 'usb' | 'tcp' | 'com'
|
||||
@@ -82,9 +82,8 @@ const TAX_SYSTEM_LABELS: Record<TaxSystem, string> = {
|
||||
// ── Subcomponents ─────────────────────────────────────────────────────────────
|
||||
|
||||
function MethodIcon({ method, size = 11 }: { method: PaymentMethod; size?: number }) {
|
||||
if (method === 'cash') return <Banknote size={size} className="text-emerald-600" />
|
||||
if (method === 'terminal') return <CreditCard size={size} className="text-blue-600" />
|
||||
return <Printer size={size} className="text-violet-600" />
|
||||
if (method === 'cash') return <Banknote size={size} className="text-emerald-600" />
|
||||
return <CreditCard size={size} className="text-blue-600" />
|
||||
}
|
||||
|
||||
// ── Component ─────────────────────────────────────────────────────────────────
|
||||
@@ -100,6 +99,7 @@ export function PosPage() {
|
||||
const [amount, setAmount] = useState<number>(0)
|
||||
const [method, setMethod] = useState<PaymentMethod>('terminal')
|
||||
const [notes, setNotes] = useState('')
|
||||
const [printFiscal, setPrintFiscal] = useState(true)
|
||||
const [lastReceipt, setLastReceipt] = useState<ReceiptRecord | null>(null)
|
||||
const [activeTab, setActiveTab] = useState<PosTab>('main')
|
||||
|
||||
@@ -151,10 +151,8 @@ export function PosPage() {
|
||||
const shiftRevenue = shiftReceipts.reduce((s, r) => s + r.amount, 0)
|
||||
const cashRevenue = shiftReceipts.filter(r => r.method === 'cash').reduce((s, r) => s + r.amount, 0)
|
||||
const termRevenue = shiftReceipts.filter(r => r.method === 'terminal').reduce((s, r) => s + r.amount, 0)
|
||||
const atolRevenue = shiftReceipts.filter(r => r.method === 'atol').reduce((s, r) => s + r.amount, 0)
|
||||
const cashCount = shiftReceipts.filter(r => r.method === 'cash').length
|
||||
const termCount = shiftReceipts.filter(r => r.method === 'terminal').length
|
||||
const atolCount = shiftReceipts.filter(r => r.method === 'atol').length
|
||||
|
||||
const handlePay = () => {
|
||||
if (!selectedRoom || amount <= 0 || !shiftOpen) return
|
||||
@@ -390,11 +388,10 @@ export function PosPage() {
|
||||
{/* Payment method */}
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Способ оплаты</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{([
|
||||
{ key: 'cash', label: 'Наличные', Icon: Banknote, cls: 'emerald' },
|
||||
{ key: 'terminal', label: 'Терминал', Icon: CreditCard, cls: 'blue' },
|
||||
{ key: 'atol', label: 'АТОЛ', Icon: Printer, cls: 'violet' },
|
||||
] as const).map(m => (
|
||||
<button
|
||||
key={m.key}
|
||||
@@ -403,19 +400,46 @@ export function PosPage() {
|
||||
'flex flex-col items-center gap-2 py-3 rounded-xl border-2 text-sm font-medium transition-colors',
|
||||
method === m.key
|
||||
? m.cls === 'emerald' ? 'bg-emerald-600 border-emerald-600 text-white'
|
||||
: m.cls === 'blue' ? 'bg-blue-600 border-blue-600 text-white'
|
||||
: 'bg-violet-600 border-violet-600 text-white'
|
||||
: 'bg-blue-600 border-blue-600 text-white'
|
||||
: 'border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:border-slate-300',
|
||||
)}
|
||||
>
|
||||
<m.Icon size={18} />
|
||||
{m.label}
|
||||
{m.key === 'atol' && <span className="text-[10px] opacity-70">интеграция</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fiscal receipt (ATOL) */}
|
||||
<div className={cn(
|
||||
'flex items-center justify-between gap-3 px-4 py-3 rounded-xl border-2 transition-colors',
|
||||
atolConnected
|
||||
? printFiscal
|
||||
? 'border-violet-400 bg-violet-50 dark:bg-violet-900/20'
|
||||
: 'border-slate-200 dark:border-slate-600'
|
||||
: 'border-slate-200 dark:border-slate-600 opacity-50',
|
||||
)}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Printer size={18} className={atolConnected && printFiscal ? 'text-violet-600' : 'text-slate-400'} />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||
Фискальный чек (АТОЛ)
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-500 dark:text-slate-400">
|
||||
{atolConnected ? 'Регистратор подключён · автопечать чека' : 'Регистратор не подключён'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => atolConnected && setPrintFiscal(v => !v)}
|
||||
disabled={!atolConnected}
|
||||
className={cn('relative w-11 h-6 rounded-full transition-colors shrink-0', printFiscal && atolConnected ? 'bg-violet-600' : 'bg-slate-300 dark:bg-slate-600')}
|
||||
>
|
||||
<div className={cn('absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform', printFiscal && atolConnected ? 'left-[22px]' : 'left-1')} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">Примечание</label>
|
||||
@@ -467,10 +491,6 @@ export function PosPage() {
|
||||
<span className="flex items-center gap-1"><CreditCard size={9} className="text-blue-600" />Терминал</span>
|
||||
<span className="font-semibold text-slate-700 dark:text-slate-300">{formatCurrency(termRevenue)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-[10px] text-slate-500">
|
||||
<span className="flex items-center gap-1"><Printer size={9} className="text-violet-600" />АТОЛ</span>
|
||||
<span className="font-semibold text-slate-700 dark:text-slate-300">{formatCurrency(atolRevenue)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-[10px] border-t border-slate-200 dark:border-slate-600 pt-0.5 mt-0.5">
|
||||
<span className="font-semibold text-slate-600 dark:text-slate-400">Итого</span>
|
||||
<span className="font-bold text-slate-900 dark:text-slate-100">{formatCurrency(shiftRevenue)}</span>
|
||||
@@ -541,14 +561,6 @@ export function PosPage() {
|
||||
<td className="py-2.5 text-right text-slate-500 text-xs">{termCount}</td>
|
||||
<td className="py-2.5 text-right font-semibold text-slate-900 dark:text-slate-100">{formatCurrency(termRevenue)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-2.5 flex items-center gap-2">
|
||||
<Printer size={14} className="text-violet-600" />
|
||||
<span className="text-slate-700 dark:text-slate-300">АТОЛ</span>
|
||||
</td>
|
||||
<td className="py-2.5 text-right text-slate-500 text-xs">{atolCount}</td>
|
||||
<td className="py-2.5 text-right font-semibold text-slate-900 dark:text-slate-100">{formatCurrency(atolRevenue)}</td>
|
||||
</tr>
|
||||
<tr className="border-t-2 border-slate-300 dark:border-slate-500">
|
||||
<td className="pt-3 pb-2 font-bold text-slate-900 dark:text-slate-100">ИТОГО</td>
|
||||
<td className="pt-3 pb-2 text-right font-bold text-slate-500">{shiftReceipts.length}</td>
|
||||
@@ -557,6 +569,12 @@ export function PosPage() {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Fiscal register info */}
|
||||
<div className="mt-3 flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<Printer size={13} className="text-violet-500" />
|
||||
Фискальный регистратор АТОЛ: {atolConnected ? <span className="text-emerald-600 font-medium">подключён</span> : <span className="text-red-500 font-medium">не подключён</span>}
|
||||
</div>
|
||||
|
||||
{/* Refunds */}
|
||||
<div className="mt-4 pt-4 border-t border-slate-200 dark:border-slate-600">
|
||||
<div className="flex justify-between text-sm">
|
||||
@@ -769,7 +787,8 @@ export function PosPage() {
|
||||
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">Оплата принята</p>
|
||||
<p className="text-3xl font-bold text-emerald-600 mt-1">{formatCurrency(lastReceipt.amount)}</p>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
{lastReceipt.method === 'cash' ? 'Наличными' : lastReceipt.method === 'terminal' ? 'Терминал' : 'АТОЛ'} · {format(lastReceipt.createdAt, 'HH:mm')}
|
||||
{lastReceipt.method === 'cash' ? 'Наличными' : 'Терминал'} · {format(lastReceipt.createdAt, 'HH:mm')}
|
||||
{printFiscal && atolConnected && <span className="ml-2 text-violet-600 font-medium">· Фискальный чек распечатан</span>}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-left bg-slate-50 dark:bg-slate-700/50 rounded-xl p-3 space-y-1">
|
||||
|
||||
Reference in New Issue
Block a user