- 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>
130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
/**
|
||
* Клиент для общения с 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),
|
||
}
|