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:
2026-09-25 13:02:40 +03:00
co-authored by Claude Opus 5.5
parent 2b92d82693
commit 8b3c2b6d63
22 changed files with 531 additions and 73 deletions
@@ -0,0 +1,58 @@
"""Ключ API Новой Почты у кассы и привязка заказа к кассе
Revision ID: 0008
Revises: 0007
Create Date: 2026-09-25
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from app.core import crypto
from app.core.config import settings
revision: str = "0008"
down_revision: str | None = "0007"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"cash_registers", sa.Column("np_api_key_enc", sa.String(length=512), nullable=True)
)
op.add_column("orders", sa.Column("cash_register_id", sa.Uuid(), nullable=True))
op.create_foreign_key(
op.f("fk_orders_cash_register_id_cash_registers"),
"orders",
"cash_registers",
["cash_register_id"],
["id"],
ondelete="SET NULL",
)
op.create_index(op.f("ix_orders_cash_register_id"), "orders", ["cash_register_id"])
# Раньше ключ НП был один на всё приложение (NOVA_POSHTA_API_KEY) — переносим
# его в кассу по умолчанию, чтобы опрос статусов не остановился после обновления.
if settings.nova_poshta_api_key:
op.get_bind().execute(
sa.text(
"UPDATE cash_registers SET np_api_key_enc = :key "
"WHERE id = (SELECT id FROM cash_registers "
"ORDER BY is_default DESC, created_at LIMIT 1)"
),
{"key": crypto.encrypt(settings.nova_poshta_api_key)},
)
def downgrade() -> None:
op.drop_index(op.f("ix_orders_cash_register_id"), table_name="orders")
op.drop_constraint(
op.f("fk_orders_cash_register_id_cash_registers"), "orders", type_="foreignkey"
)
op.drop_column("orders", "cash_register_id")
op.drop_column("cash_registers", "np_api_key_enc")