"""Кассы Checkbox. Только администратор; секреты — только на запись.""" from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Request, status from sqlalchemy import select, update from app.api.deps import AdminUser, CheckboxClientDep, SessionDep, require_admin from app.core import crypto from app.db.models.audit import AuditAction from app.db.models.cash_register import CashRegister from app.schemas.receipts import CashRegisterCreate, CashRegisterOut, CashRegisterUpdate from app.services import audit from app.services.checkbox.client import CheckboxError from app.services.receipts import credentials router = APIRouter( prefix="/cash-registers", tags=["cash-registers"], dependencies=[Depends(require_admin)] ) async def _clear_default(session: SessionDep, keep_id: uuid.UUID) -> None: await session.execute( update(CashRegister).where(CashRegister.id != keep_id).values(is_default=False) ) @router.get("", response_model=list[CashRegisterOut]) async def list_cash_registers(session: SessionDep) -> list[CashRegisterOut]: registers = await session.scalars(select(CashRegister).order_by(CashRegister.created_at)) return [CashRegisterOut.from_register(register) for register in registers] @router.post("", response_model=CashRegisterOut, status_code=status.HTTP_201_CREATED) async def create_cash_register( payload: CashRegisterCreate, request: Request, session: SessionDep, actor: AdminUser ) -> CashRegisterOut: register = CashRegister( id=uuid.uuid4(), name=payload.name, fiscal_number=payload.fiscal_number, license_key_enc=crypto.encrypt(payload.license_key), cashier_pin_enc=crypto.encrypt(payload.pin_code), tax_codes=payload.tax_codes, is_active=True, is_default=payload.is_default, ) if register.is_default: await _clear_default(session, register.id) session.add(register) await audit.record( session, action=AuditAction.CASH_REGISTER_CREATED, user=actor, entity_type="cash_register", entity_id=register.id, payload={"name": register.name, "fiscal_number": register.fiscal_number}, request=request, ) await session.commit() return CashRegisterOut.from_register(register) @router.patch("/{register_id}", response_model=CashRegisterOut) async def update_cash_register( register_id: uuid.UUID, payload: CashRegisterUpdate, request: Request, session: SessionDep, actor: AdminUser, ) -> CashRegisterOut: register = await session.get(CashRegister, register_id) if register is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Касса не найдена") changes = payload.model_dump(exclude_unset=True) if "license_key" in changes: register.license_key_enc = crypto.encrypt(changes["license_key"]) if "pin_code" in changes: register.cashier_pin_enc = crypto.encrypt(changes["pin_code"]) if "fiscal_number" in changes: register.fiscal_number = changes["fiscal_number"] for field in ("name", "tax_codes", "is_active", "is_default"): if changes.get(field) is not None: setattr(register, field, changes[field]) if changes.get("is_default"): await _clear_default(session, register.id) await audit.record( session, action=AuditAction.CASH_REGISTER_UPDATED, user=actor, entity_type="cash_register", entity_id=register.id, payload=changes, # license_key / pin_code редактируются в audit._sanitize request=request, ) await session.commit() return CashRegisterOut.from_register(register) @router.post("/{register_id}/check", status_code=status.HTTP_204_NO_CONTENT) async def check_cash_register( register_id: uuid.UUID, session: SessionDep, checkbox: CheckboxClientDep ) -> None: """Пробный вход кассира в Checkbox — проверка ключа лицензии и PIN.""" register = await session.get(CashRegister, register_id) if register is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Касса не найдена") try: await checkbox.sign_in(credentials(register)) except (CheckboxError, crypto.DecryptionError) as exc: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Checkbox: {exc}" ) from exc