Bind Nova Poshta API key to cash register (#3)
Each cash register stores its own encrypted NP API key. Status polling uses register keys and binds an order to the register whose key sees the TTN as its own (PhoneSender present); ETTN receipts are created from that register. Migration 0008 moves the old NOVA_POSHTA_API_KEY into the default register. Closes #3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ interface FormState {
|
||||
fiscal_number: string
|
||||
license_key: string
|
||||
pin_code: string
|
||||
np_api_key: string
|
||||
tax_codes: string
|
||||
is_default: boolean
|
||||
}
|
||||
@@ -28,6 +29,7 @@ const EMPTY_FORM: FormState = {
|
||||
fiscal_number: '',
|
||||
license_key: '',
|
||||
pin_code: '',
|
||||
np_api_key: '',
|
||||
tax_codes: '',
|
||||
is_default: true,
|
||||
}
|
||||
@@ -75,6 +77,7 @@ export function CashRegistersPage() {
|
||||
fiscal_number: register.fiscal_number ?? '',
|
||||
license_key: '',
|
||||
pin_code: '',
|
||||
np_api_key: '',
|
||||
tax_codes: register.tax_codes.join(', '),
|
||||
is_default: register.is_default,
|
||||
})
|
||||
@@ -88,7 +91,7 @@ export function CashRegistersPage() {
|
||||
async function handleSubmit(event: FormEvent) {
|
||||
event.preventDefault()
|
||||
if (editingId) {
|
||||
// Пустые ключ/PIN при редактировании — «не менять».
|
||||
// Пустые ключи/PIN при редактировании — «не менять».
|
||||
const payload: CashRegisterUpdate = {
|
||||
name: form.name,
|
||||
fiscal_number: form.fiscal_number || null,
|
||||
@@ -97,6 +100,7 @@ export function CashRegistersPage() {
|
||||
}
|
||||
if (form.license_key) payload.license_key = form.license_key
|
||||
if (form.pin_code) payload.pin_code = form.pin_code
|
||||
if (form.np_api_key) payload.np_api_key = form.np_api_key
|
||||
await run(() => updateCashRegister(editingId, payload), 'Касса обновлена')
|
||||
} else {
|
||||
await run(
|
||||
@@ -106,6 +110,7 @@ export function CashRegistersPage() {
|
||||
fiscal_number: form.fiscal_number || null,
|
||||
license_key: form.license_key,
|
||||
pin_code: form.pin_code,
|
||||
np_api_key: form.np_api_key,
|
||||
tax_codes: parseTaxCodes(form.tax_codes),
|
||||
is_default: form.is_default,
|
||||
}),
|
||||
@@ -145,6 +150,7 @@ export function CashRegistersPage() {
|
||||
<th>Название</th>
|
||||
<th>Фиск. номер</th>
|
||||
<th>Ключ лицензии</th>
|
||||
<th>Ключ Новой Почты</th>
|
||||
<th>Налоги</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
@@ -153,21 +159,21 @@ export function CashRegistersPage() {
|
||||
<tbody>
|
||||
{isLoading && (
|
||||
<tr>
|
||||
<td colSpan={6} className="orders-empty">
|
||||
<td colSpan={7} className="orders-empty">
|
||||
Загрузка…
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{isError && (
|
||||
<tr>
|
||||
<td colSpan={6} className="orders-empty">
|
||||
<td colSpan={7} className="orders-empty">
|
||||
Не удалось загрузить кассы
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{registers?.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={6} className="orders-empty">
|
||||
<td colSpan={7} className="orders-empty">
|
||||
Кассы не настроены — чеки создавать нельзя
|
||||
</td>
|
||||
</tr>
|
||||
@@ -180,6 +186,7 @@ export function CashRegistersPage() {
|
||||
</td>
|
||||
<td>{register.fiscal_number || '—'}</td>
|
||||
<td>{register.license_key_masked}</td>
|
||||
<td>{register.np_api_key_masked ?? <span className="cr-missing">не задан</span>}</td>
|
||||
<td>{register.tax_codes.length ? register.tax_codes.join(', ') : 'без налога'}</td>
|
||||
<td>
|
||||
<span className={`orders-status orders-status--${register.is_active ? 'delivered' : 'new'}`}>
|
||||
@@ -249,6 +256,16 @@ export function CashRegistersPage() {
|
||||
onChange={(e) => setForm({ ...form, pin_code: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Ключ API Новой Почты (посылки этого кабинета пробиваются через эту кассу)
|
||||
<input
|
||||
required={!editingId}
|
||||
autoComplete="off"
|
||||
placeholder={editingId ? 'не менять' : ''}
|
||||
value={form.np_api_key}
|
||||
onChange={(e) => setForm({ ...form, np_api_key: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Коды налогов (через запятую, пусто — без налога)
|
||||
<input value={form.tax_codes} onChange={(e) => setForm({ ...form, tax_codes: e.target.value })} />
|
||||
@@ -259,7 +276,7 @@ export function CashRegistersPage() {
|
||||
checked={form.is_default}
|
||||
onChange={(e) => setForm({ ...form, is_default: e.target.checked })}
|
||||
/>
|
||||
Основная касса (по ней создаются чеки)
|
||||
Основная касса (первой проверяет новые посылки)
|
||||
</label>
|
||||
<div className="cr-form-actions">
|
||||
<button type="submit" className="orders-create-btn" disabled={busy}>
|
||||
|
||||
Reference in New Issue
Block a user