7 Commits

Author SHA1 Message Date
Andrey Epifancev
b17176f97c fix: «сегодня» для effective_date — дата по Москве (#9 п.6, follow-up #8)
`_today()` в `services/legal_entities.py` берёт московскую дату
(`common/clock.py`: `moscow_date`, `MOSCOW_TZ` = Europe/Moscow; без базы
часовых поясов — фиксированный UTC+3, Москва без перехода на летнее время с
2014 г.). Тесты на границе суток: 23:30 МСК (20:30 UTC) — ещё 4-е, 00:30 МСК
(21:30 UTC того же дня) — уже 5-е, и 4-е становится прошлым. README: даты
перевода/вывода — по Москве.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 13:29:00 +04:00
Andrey Epifancev
ef7458eddb feat: аудит входа LOGIN / LOGIN_FAILED (OQ-64, #9 п.5)
Раздел SESSION, object_type app_user, IP и user-agent запроса
(`services/session_audit.py`):
- LOGIN — выдача dev-токена (в транзакции запроса) и первый успешный
  `current_user` пользователя после старта процесса (`SessionAudit.logged_in`,
  в памяти процесса; параллельные первые запросы — одно событие);
- LOGIN_FAILED — 401 (токен не принят: user_id пуст, причина без токена) и 403
  аутентификации (не зарегистрирован / отключён / неоднозначен: email в
  description, user_id — если найден). `AuthenticationFailedError` (подкласс
  HTTPException) обрабатывается после отката транзакции запроса, запись — в
  отдельной транзакции `session_transaction`, поэтому остаётся в журнале.
Без заголовка Authorization и 403 «недостаточно прав» — не события входа;
ошибка записи аудита входа ответ не меняет.

Тесты: `test_login_audit.py` (в т.ч. фиксация при откате запроса на
`committed_app`); счётчики следов импорта не учитывают события SESSION.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 13:27:38 +04:00
Andrey Epifancev
070a398c65 fix(api): гонка уникальных ограничений отдаёт тот же 409 с кодом, что и pre-check (T6.5, T7.1, T8.2, T12.7, T15.3, T15.4)
Реестр имя ограничения PostgreSQL -> (код, постоянный текст) в common/constraint_conflicts.py;
integrity_error_handler для unique_violation (23505) по известному ограничению отдаёт
{"detail": {"code", "message"}}, иначе прежняя общая строка. Имя берётся из полей драйвера
(asyncpg __cause__.constraint_name, psycopg diag), не из текста ошибки; resolved_code пишется в лог.
Тесты: unit обработчика/реестра (ключи есть в Base.metadata), интеграционные гонки (ГК/ЮЛ crm_id,
ИНН, алиас, email, членство, откат и следующий запрос, FK остаётся общим 409).
T15.3: README шаг 4 импорта; T15.4: test_components_have_no_duplicated_models.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:02:14 +04:00
Andrey Epifancev
e3ea074e4d refactor(services): общий utc_now вместо локальных _now в сервисах справочников
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:08:04 +04:00
Andrey Epifancev
5976986e1b feat(api): транзакция на запрос, RequestAudit, доменные ошибки 404/409/422, Page
- get_async_session: одна транзакция на запрос (commit при успехе, rollback при исключении), R-1;
  объявляется только через SessionDep (scope="function") — commit до отправки ответа, одна сессия
  на все зависимости запроса; current_user и dev-эмитент переведены на SessionDep.
- DomainError/NotFoundError/ConflictError/DomainValidationError + обработчик
  {"detail": {"code", "message", ...}}; ответы HTTPException и ошибок БД не меняются.
- RequestAudit/get_request_audit: AuditService.record с user_id, ip_address, user_agent запроса.
- schemas/common.py: Page[T], PageParams (limit 1..200, по умолчанию 50; offset >= 0), ErrorDetail.
- tests/test_uow_and_errors.py: формат ошибок, пагинация, аудит, scope сессии во всех маршрутах,
  настоящий commit/rollback на временной БД, commit до http.response.start, ошибка commit -> 409.
- План: Task 2 отмечена; tasks.md: 1.2 отмечен.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:03:07 +04:00
Andrey Epifancev
72415ab891 style: ruff format и правки линтера на каркасе шаблона
Без смены поведения: форматирование, сортировка импортов, удаление неиспользуемых
импортов, __all__ в src/db, datetime.UTC, HTTPStatus.NOT_FOUND вместо 404,
аннотация lvl в logger, noqa F401 на проверке доступности raisa_fastapi_protected_api,
mypy: miniopy_async не подтягивается (follow_imports=skip).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:02:19 +04:00
Andrey Epifancev
ba922225ab chore: каркас из шаблона команды (DKB_ARMKA@ba172f8, доменный код удалён) 2026-10-02 23:15:37 +04:00