feat: касса — полная переработка PosPage + интеграция с агентом
- PosPage.tsx: полный редизайн вокруг реальной кассовой работы • Автодетект агента (localhost:9000), статус смены каждые 15с • Экраны: агент не подключён / смена закрыта / смена истекла / касса • Шапка смены: №, длительность, остаток наличных, статус ОФД • Кнопки: X-отчёт, Внести/Изъять наличные, Закрыть смену • Левая панель: гости с балансом + быстрые услуги • Правая панель: чек с количеством, итого, выбор наличные/карта • Экран успеха после пробития чека с номером ФД - agent.ts: клиент localhost:9000 — identity + все kkt операции - index.css: btn-danger класс Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,14 @@
|
||||
disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply inline-flex items-center gap-2 px-4 py-2 rounded-lg
|
||||
bg-red-600 hover:bg-red-700
|
||||
text-white text-sm font-medium
|
||||
transition-colors duration-150
|
||||
disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply inline-flex items-center gap-2 px-4 py-2 rounded-lg
|
||||
bg-white dark:bg-slate-800
|
||||
|
||||
129
src/lib/agent.ts
Normal file
129
src/lib/agent.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Клиент для общения с HotelSync Agent на localhost:9000
|
||||
* Все кассовые операции идут напрямую через агент, минуя сервер
|
||||
*/
|
||||
|
||||
const AGENT_URL = 'http://localhost:9000'
|
||||
const TIMEOUT = 30000 // 30 секунд для кассовых операций
|
||||
|
||||
async function agentReq<T>(
|
||||
method: 'GET' | 'POST',
|
||||
path: string,
|
||||
body?: object,
|
||||
timeout = TIMEOUT,
|
||||
): Promise<T> {
|
||||
const res = await fetch(`${AGENT_URL}${path}`, {
|
||||
method,
|
||||
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
})
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
// ── Типы ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AgentIdentity {
|
||||
agent_id: string
|
||||
workstation_id: string
|
||||
workstation_name: string
|
||||
hostname: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ShiftStatus {
|
||||
state: 'closed' | 'open' | 'expired'
|
||||
shift_number: number
|
||||
opened_at?: string
|
||||
duration_min?: number
|
||||
cash_sum?: number
|
||||
receipt_count?: number
|
||||
cashier_name?: string
|
||||
}
|
||||
|
||||
export interface KktInfo {
|
||||
serial_number: string
|
||||
model: string
|
||||
fn_number: string
|
||||
fn_expires_at?: string
|
||||
ofd_status: 'ok' | 'error' | 'no_connection'
|
||||
version: string
|
||||
}
|
||||
|
||||
export interface KktStatus {
|
||||
shift?: ShiftStatus
|
||||
info?: KktInfo
|
||||
dto_connected: boolean
|
||||
dto_version?: string
|
||||
}
|
||||
|
||||
export interface CashierInfo {
|
||||
name: string
|
||||
inn?: string
|
||||
}
|
||||
|
||||
export interface ReceiptItem {
|
||||
name: string
|
||||
quantity: number
|
||||
price: number
|
||||
vat?: 'none' | 'vat0' | 'vat10' | 'vat20'
|
||||
payment_object?: 'commodity' | 'service'
|
||||
}
|
||||
|
||||
export interface ReceiptData {
|
||||
type: 'sell' | 'sell_return'
|
||||
items: ReceiptItem[]
|
||||
total: number
|
||||
payment_type: 'cash' | 'card' | 'prepaid'
|
||||
cashier?: CashierInfo
|
||||
customer_email?: string
|
||||
customer_phone?: string
|
||||
tax_system?: number
|
||||
}
|
||||
|
||||
export interface AgentResult {
|
||||
ok: boolean
|
||||
error?: string
|
||||
receipt_number?: string
|
||||
fiscal_sign?: string
|
||||
shift_number?: number
|
||||
}
|
||||
|
||||
// ── API ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Проверяет что агент запущен и возвращает рабочее место. Timeout 500ms. */
|
||||
export async function getIdentity(): Promise<AgentIdentity | null> {
|
||||
try {
|
||||
const data = await agentReq<AgentIdentity>('GET', '/identity', undefined, 500)
|
||||
if (data.error) return null
|
||||
return data
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const kkt = {
|
||||
status: () =>
|
||||
agentReq<KktStatus>('GET', '/kkt/status', undefined, 5000),
|
||||
|
||||
openShift: (cashier: CashierInfo, tax_system = 1) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/shift/open', { cashier, tax_system }),
|
||||
|
||||
closeShift: (cashier: CashierInfo) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/shift/close', { cashier }),
|
||||
|
||||
xReport: () =>
|
||||
agentReq<AgentResult>('POST', '/kkt/report/x', {}),
|
||||
|
||||
cashIn: (amount: number, cashier: CashierInfo) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/cash-in', { amount, cashier }),
|
||||
|
||||
cashOut: (amount: number, cashier: CashierInfo) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/cash-out', { amount, cashier }),
|
||||
|
||||
printReceipt: (data: ReceiptData) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/receipt', data),
|
||||
|
||||
printReturn: (data: ReceiptData) =>
|
||||
agentReq<AgentResult>('POST', '/kkt/return', data),
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user