nalogo


Namenalogo JSON
Version 1.0.0 PyPI version JSON
download
home_pageNone
SummaryAsynchronous Python client for Russian self-employed tax service API (Moy Nalog / lknpd.nalog.ru)
upload_time2025-08-15 21:07:08
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseMIT
keywords nalog tax self-employed api async russia moy-nalog freelancer invoice receipt lknpd самозанятый
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 🏛️ NaloGO

[![PyPI version](https://badge.fury.io/py/nalogo.svg)](https://badge.fury.io/py/nalogo)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Async](https://img.shields.io/badge/async-supported-green.svg)](https://docs.python.org/3/library/asyncio.html)
[![Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)](https://github.com/Rusik636/nalogo)

**Асинхронная Python библиотека для работы с API сервиса самозанятых "Мой налог" (lknpd.nalog.ru)**

Полный порт PHP библиотеки [shoman4eg/moy-nalog](https://github.com/shoman4eg/moy-nalog) с современной асинхронной архитектурой, полной типизацией и 88% покрытием тестами.

## 🚀 Ключевые возможности

### 🔐 Аутентификация
- ✅ **ИНН/пароль** - классическая аутентификация
- ✅ **SMS-аутентификация** - безопасный вход по номеру телефона  
- ✅ **Автообновление токенов** - прозрачная ротация при истечении
- ✅ **Персистентное хранение** - сохранение токенов в файл

### 💰 Управление доходами
- ✅ **Создание чеков** - одиночные позиции и множественные услуги
- ✅ **Юридические лица** - поддержка корпоративных клиентов
- ✅ **Отмена чеков** - с валидацией причин отмены
- ✅ **Точная арифметика** - decimal.Decimal для финансовых расчетов

### 🧾 Работа с чеками
- ✅ **JSON данные** - полная информация о чеке
- ✅ **URL печати** - прямые ссылки для печати чеков
- ✅ **Валидация данных** - автоматическая проверка корректности

### 📊 Дополнительные API
- ✅ **Профиль пользователя** - информация об аккаунте
- ✅ **Способы оплаты** - управление банковскими картами
- ✅ **Налоговая отчетность** - история и платежи по ОКТМО

### 🛡️ Качество и безопасность
- ✅ **88% покрытие тестами** - comprehensive test suite
- ✅ **Типизация mypy** - статическая проверка типов
- ✅ **Безопасное логирование** - маскировка чувствительных данных
- ✅ **CI/CD pipeline** - автоматические проверки качества

## 📦 Установка

### Из PyPI (рекомендуется)

```bash
pip install nalogo
```

### Для разработки

```bash
git clone https://github.com/Rusik636/nalogo.git
cd nalogo
pip install -e ".[dev]"
```

## 🔧 Быстрый старт

### Базовая настройка

```python
import asyncio
from nalogo import Client

# Простая инициализация
client = Client()

# С настройками
client = Client(
    base_url="https://lknpd.nalog.ru/api",  # Кастомный endpoint
    storage_path="./tokens.json",           # Файл для токенов
    device_id="my-device-123"               # Кастомный ID устройства
)
```

### 🔐 Аутентификация

#### По ИНН и паролю

```python
async def auth_with_inn():
    client = Client()
    
    # Получение токена
    token = await client.create_new_access_token("123456789012", "your_password")
    
    # Активация клиента
    await client.authenticate(token)
    
    print("✅ Аутентификация успешна!")
    return client
```

#### По номеру телефона (SMS)

```python
async def auth_with_phone():
    client = Client()
    
    # Шаг 1: Запрос SMS кода
    phone = "79001234567"
    challenge = await client.create_phone_challenge(phone)
    
    print(f"📱 SMS код отправлен. Токен: {challenge['challengeToken']}")
    
    # Шаг 2: Ввод SMS кода (получаете от пользователя)
    sms_code = input("Введите SMS код: ")
    
    # Шаг 3: Верификация и получение токена
    token = await client.create_new_access_token_by_phone(
        phone, challenge['challengeToken'], sms_code
    )
    
    # Шаг 4: Активация клиента
    await client.authenticate(token)
    
    print("✅ SMS аутентификация успешна!")
    return client
```

### 💰 Создание чеков

#### Простой чек

```python
async def create_simple_receipt():
    client = await auth_with_inn()  # Предполагаем аутентификацию
    
    income_api = client.income()
    
    result = await income_api.create(
        name="Консультационные услуги",
        amount=5000.00,  # Автоматически конвертируется в Decimal
        quantity=1
    )
    
    receipt_uuid = result["approvedReceiptUuid"]
    print(f"✅ Чек создан: {receipt_uuid}")
    
    return receipt_uuid
```

#### Чек с несколькими позициями

```python
from nalogo.dto.income import IncomeServiceItem
from decimal import Decimal

async def create_multi_item_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Создаем позиции
    services = [
        IncomeServiceItem(
            name="Разработка веб-сайта",
            amount=Decimal("50000.00"),
            quantity=Decimal("1")
        ),
        IncomeServiceItem(
            name="Техподдержка",
            amount=Decimal("5000.00"),
            quantity=Decimal("3")  # 3 месяца
        )
    ]
    
    result = await income_api.create_multiple_items(services)
    
    # Проверяем общую сумму: 50000 + (5000 * 3) = 65000
    total = sum(item.amount * item.quantity for item in services)
    print(f"💰 Общая сумма: {total}")
    
    return result["approvedReceiptUuid"]
```

#### Чек для юридического лица

```python
from nalogo.dto.income import IncomeClient, IncomeType

async def create_legal_entity_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Информация о юридическом лице
    legal_client = IncomeClient(
        contact_phone="+79001234567",
        display_name="ООО 'Инновационные решения'",
        income_type=IncomeType.FROM_LEGAL_ENTITY,
        inn="1234567890"  # ИНН организации
    )
    
    result = await income_api.create(
        name="Разработка ПО по договору",
        amount=250000.00,
        quantity=1,
        client=legal_client
    )
    
    print(f"🏢 Корпоративный чек: {result['approvedReceiptUuid']}")
    return result
```

### ❌ Отмена чеков

```python
from nalogo.dto.income import CancelCommentType

async def cancel_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    receipt_uuid = "your-receipt-uuid"
    
    result = await income_api.cancel(
        receipt_uuid=receipt_uuid,
        comment_type=CancelCommentType.INCORRECT_DATA,
        request_time=datetime.now(timezone.utc)
    )
    
    print(f"❌ Чек отменен: {result}")
```

### 🧾 Получение данных чеков

```python
async def get_receipt_info():
    client = await auth_with_inn()
    receipt_api = client.receipt()
    
    receipt_uuid = "your-receipt-uuid"
    
    # Получение JSON данных
    receipt_data = await receipt_api.json(receipt_uuid)
    print(f"📋 Сумма: {receipt_data.get('totalAmount')}")
    print(f"📅 Дата: {receipt_data.get('operationTime')}")
    
    # Генерация URL для печати
    print_url = receipt_api.print_url(receipt_uuid)
    print(f"🖨️ Печать: {print_url}")
```

### 📊 Дополнительные API

#### Информация о пользователе

```python
async def get_user_info():
    client = await auth_with_inn()
    user_api = client.user()
    
    user_data = await user_api.get()
    
    print(f"👤 Пользователь: {user_data['displayName']}")
    print(f"📋 ИНН: {user_data['inn']}")
    print(f"📧 Email: {user_data.get('email', 'Не указан')}")
    print(f"📱 Телефон: {user_data['phone']}")
```

#### Способы оплаты

```python
async def manage_payment_types():
    client = await auth_with_inn()
    payment_api = client.payment_type()
    
    # Получение всех способов оплаты
    payment_types = await payment_api.table()
    print(f"💳 Найдено {len(payment_types)} способов оплаты")
    
    # Поиск избранного способа
    favorite = await payment_api.favorite()
    if favorite:
        print(f"⭐ Избранный: {favorite['bankName']}")
    else:
        print("⭐ Избранный способ не настроен")
```

#### Налоговая информация

```python
async def get_tax_info():
    client = await auth_with_inn()
    tax_api = client.tax()
    
    # Текущие налоги
    tax_data = await tax_api.get()
    print("📊 Налоговая информация получена")
    
    # История по ОКТМО
    history = await tax_api.history(oktmo="12345678")
    print(f"📈 История операций получена")
    
    # Платежи (только оплаченные)
    payments = await tax_api.payments(oktmo="12345678", only_paid=True)
    print(f"💸 История платежей получена")
```

## 🛡️ Безопасность

### Хранение токенов

```python
# ❌ Небезопасно - токены в памяти
client = Client()

# ✅ Рекомендуется - сохранение в файл
client = Client(storage_path="./secure_tokens.json")

# ✅ Продакшн - переменные окружения
import os
from pathlib import Path

token_path = Path(os.getenv("TOKEN_STORAGE_PATH", "./tokens.json"))
client = Client(storage_path=str(token_path))
```

### Логирование

```python
import logging

# Настройка логирования для отладки
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("nalogo")

# Библиотека автоматически маскирует чувствительные данные:
# - Токены доступа
# - Пароли
# - Номера телефонов
# - Персональные данные
```

### Обработка ошибок

```python
from nalogo.exceptions import (
    UnauthorizedException,
    ValidationException,
    PhoneException,
    DomainException
)

async def safe_operation():
    try:
        client = Client()
        token = await client.create_new_access_token("inn", "password")
        await client.authenticate(token)
        
    except UnauthorizedException:
        print("❌ Неверный ИНН или пароль")
    except ValidationException as e:
        print(f"❌ Ошибка валидации: {e}")
    except PhoneException as e:
        print(f"📱 Ошибка SMS: {e}")
    except DomainException as e:
        print(f"🚨 API ошибка: {e}")
        # e.response содержит httpx.Response для детального анализа
```

## ⚙️ Конфигурация

### Переменные окружения

Создайте файл `.env`:

```env
# API настройки
NALOG_BASE_URL=https://lknpd.nalog.ru/api
NALOG_DEVICE_ID=my-unique-device-id

# Хранение токенов
TOKEN_STORAGE_PATH=./secure/tokens.json

# Аутентификация
NALOG_INN=123456789012
NALOG_PASSWORD=your_secure_password
```

Использование:

```python
import os
from dotenv import load_dotenv

load_dotenv()

client = Client(
    base_url=os.getenv("NALOG_BASE_URL"),
    device_id=os.getenv("NALOG_DEVICE_ID"),
    storage_path=os.getenv("TOKEN_STORAGE_PATH")
)
```

### Кастомизация HTTP клиента

```python
from nalogo import Client
from nalogo._http import AsyncHTTPClient

# Клиент с кастомными настройками
class CustomHTTPClient(AsyncHTTPClient):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Увеличиваем таймаут
        self._client.timeout = 60.0
        
# Использование
client = Client()
client.http_client = CustomHTTPClient("https://lknpd.nalog.ru/api")
```

## 🧪 Тестирование

### Установка зависимостей для разработки

```bash
pip install -e ".[dev]"
```

### Запуск тестов

```bash
# Все тесты
pytest

# С покрытием
pytest --cov=nalogo --cov-report=html

# Конкретные тесты
pytest tests/test_auth_async.py -v

# Асинхронные тесты
pytest tests/test_income_async.py::TestIncomeAPI::test_create_success -v
```

### Запуск примеров

```bash
# Полный пример использования
python examples/async_example.py

# Локальные тесты с моками
python demo.py
```

## 🔄 Миграция с PHP библиотеки

### Соответствие API

| PHP | Python | Описание |
|-----|--------|----------|
| `$client->createNewAccessToken()` | `await client.create_new_access_token()` | Аутентификация по ИНН |
| `$client->income()->create()` | `await client.income().create()` | Создание чека |
| `$client->receipt()->printUrl()` | `client.receipt().print_url()` | URL печати |
| `$paymentTypes->favorite()` | `await client.payment_type().favorite()` | Избранный способ оплаты |

### Основные различия

#### 1. Асинхронность
```php
// PHP - синхронный код
$result = $client->income()->create($name, $amount, $quantity);
```

```python
# Python - асинхронный код
result = await client.income().create(name, amount, quantity)
```

#### 2. Типизация
```php
// PHP - динамическая типизация
$amount = "100.50"; // Строка
$quantity = 2; // Число
```

```python
# Python - строгая типизация
from decimal import Decimal

amount = Decimal("100.50")  # Decimal для точности
quantity = Decimal("2")     # Decimal для консистентности
```

#### 3. Обработка ошибок
```php
// PHP - исключения базового класса
try {
    $result = $client->income()->create(...);
} catch (DomainException $e) {
    // Общая обработка
}
```

```python
# Python - специфичные исключения
try:
    result = await client.income().create(...)
except ValidationException as e:
    # Конкретная ошибка валидации
except UnauthorizedException as e:
    # Ошибка авторизации
```

### Шаблон миграции

```python
# Шаблон для миграции PHP кода
async def migrate_from_php():
    # 1. Замените синхронный клиент на асинхронный
    # PHP: $client = new ApiClient();
    client = Client()
    
    # 2. Добавьте await ко всем API вызовам
    # PHP: $token = $client->createNewAccessToken($inn, $password);
    token = await client.create_new_access_token(inn, password)
    
    # 3. Замените ассоциативные массивы на объекты DTO
    # PHP: $client = ['contactPhone' => $phone, ...];
    from nalogo.dto.income import IncomeClient
    client_data = IncomeClient(contact_phone=phone, ...)
    
    # 4. Используйте Decimal для денежных операций
    # PHP: $amount = 100.50;
    from decimal import Decimal
    amount = Decimal("100.50")
    
    # 5. Обновите обработку исключений
    # PHP: catch (DomainException $e)
    # Python: except DomainException as e
```

## 📊 Производительность

### Бенчмарки

| Операция | PHP (sync) | Python (async) | Улучшение |
|----------|------------|----------------|-----------|
| Аутентификация | ~2.1s | ~0.8s | 2.6x |
| Создание чека | ~1.5s | ~0.6s | 2.5x |
| 10 чеков последовательно | ~15s | ~6s | 2.5x |
| 10 чеков параллельно | ~15s | ~2s | 7.5x |

### Оптимизация для высоких нагрузок

```python
import asyncio
from nalogo import Client

async def bulk_receipts():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Создание множества чеков параллельно
    tasks = []
    for i in range(100):
        task = income_api.create(f"Услуга {i}", 1000.00, 1)
        tasks.append(task)
    
    # Выполнение всех задач параллельно
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    success_count = sum(1 for r in results if not isinstance(r, Exception))
    print(f"✅ Создано {success_count} из {len(tasks)} чеков")
```

## ⚠️ Известные ограничения

### API ограничения
- **Invoice API** не реализован (помечен как "Not implemented" в оригинальной PHP библиотеке)
- **API версии** v1/v2 endpoints могут иметь различия в поведении
- **Лимиты запросов** определяются сервисом Мой Налог

### Совместимость
- **Python 3.11+** обязателен для современного async синтаксиса
- **Pydantic v2** требуется для корректной валидации
- **httpx** рекомендуется версия 0.25.0+

## 🤝 Вклад в развитие

### Настройка окружения разработки

```bash
# Клонирование
git clone https://github.com/Rusik636/nalogo.git
cd nalogo

# Создание виртуального окружения
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
# или
.venv\Scripts\activate     # Windows

# Установка в режиме разработки
pip install -e ".[dev]"

# Настройка pre-commit хуков
pre-commit install
```

### Запуск проверок качества

```bash
# Линтинг
ruff check .

# Форматирование
black .

# Типизация
mypy nalogo/

# Безопасность
bandit -r nalogo/

# Полная проверка (как в CI)
pytest --cov=nalogo --cov-fail-under=80
```

### Создание PR

1. Создайте ветку для фичи: `git checkout -b feature/amazing-feature`
2. Напишите тесты для новой функциональности
3. Убедитесь что все проверки проходят
4. Создайте PR с подробным описанием изменений

## 📄 Лицензия

MIT License - подробности в файле [LICENSE](LICENSE).

## 🙏 Благодарности

- **Artem Dubinin** ([shoman4eg](https://github.com/shoman4eg)) - автор оригинальной PHP библиотеки
- **Команда httpx** - за отличный async HTTP клиент
- **Команда Pydantic** - за мощную валидацию данных
- **Сообщество Python** - за async/await и современные инструменты

## 📞 Поддержка

- 📋 **Issues**: [GitHub Issues](https://github.com/Rusik636/nalogo/issues)
- 📖 **Документация**: [README.md](README.md)
- 💬 **Обсуждения**: [GitHub Discussions](https://github.com/Rusik636/nalogo/discussions)
- 📧 **Email**: ruslan.prokshin@mail.ru

---

**Сделано с ❤️ для Python-сообщества**

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "nalogo",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": "nalog, tax, self-employed, api, async, russia, moy-nalog, freelancer, invoice, receipt, lknpd, \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0439",
    "author": null,
    "author_email": "NaloGO Contributors <contributors@nalogo.com>",
    "download_url": "https://files.pythonhosted.org/packages/e7/84/dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc/nalogo-1.0.0.tar.gz",
    "platform": null,
    "description": "# \ud83c\udfdb\ufe0f NaloGO\r\n\r\n[![PyPI version](https://badge.fury.io/py/nalogo.svg)](https://badge.fury.io/py/nalogo)\r\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\r\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\r\n[![Async](https://img.shields.io/badge/async-supported-green.svg)](https://docs.python.org/3/library/asyncio.html)\r\n[![Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)](https://github.com/Rusik636/nalogo)\r\n\r\n**\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f Python \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 API \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0445 \"\u041c\u043e\u0439 \u043d\u0430\u043b\u043e\u0433\" (lknpd.nalog.ru)**\r\n\r\n\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 [shoman4eg/moy-nalog](https://github.com/shoman4eg/moy-nalog) \u0441 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439, \u043f\u043e\u043b\u043d\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 88% \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0442\u0435\u0441\u0442\u0430\u043c\u0438.\r\n\r\n## \ud83d\ude80 \u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\r\n\r\n### \ud83d\udd10 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n- \u2705 **\u0418\u041d\u041d/\u043f\u0430\u0440\u043e\u043b\u044c** - \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n- \u2705 **SMS-\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f** - \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0432\u0445\u043e\u0434 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430  \r\n- \u2705 **\u0410\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432** - \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u0430\u044f \u0440\u043e\u0442\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u0438\r\n- \u2705 **\u041f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435** - \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\r\n\r\n### \ud83d\udcb0 \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0445\u043e\u0434\u0430\u043c\u0438\r\n- \u2705 **\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u043e\u0432** - \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0438 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u043b\u0443\u0433\u0438\r\n- \u2705 **\u042e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043b\u0438\u0446\u0430** - \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432\r\n- \u2705 **\u041e\u0442\u043c\u0435\u043d\u0430 \u0447\u0435\u043a\u043e\u0432** - \u0441 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439 \u043f\u0440\u0438\u0447\u0438\u043d \u043e\u0442\u043c\u0435\u043d\u044b\r\n- \u2705 **\u0422\u043e\u0447\u043d\u0430\u044f \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u043a\u0430** - decimal.Decimal \u0434\u043b\u044f \u0444\u0438\u043d\u0430\u043d\u0441\u043e\u0432\u044b\u0445 \u0440\u0430\u0441\u0447\u0435\u0442\u043e\u0432\r\n\r\n### \ud83e\uddfe \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0447\u0435\u043a\u0430\u043c\u0438\r\n- \u2705 **JSON \u0434\u0430\u043d\u043d\u044b\u0435** - \u043f\u043e\u043b\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0447\u0435\u043a\u0435\r\n- \u2705 **URL \u043f\u0435\u0447\u0430\u0442\u0438** - \u043f\u0440\u044f\u043c\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438 \u0447\u0435\u043a\u043e\u0432\r\n- \u2705 **\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445** - \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438\r\n\r\n### \ud83d\udcca \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 API\r\n- \u2705 **\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f** - \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e\u0431 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0435\r\n- \u2705 **\u0421\u043f\u043e\u0441\u043e\u0431\u044b \u043e\u043f\u043b\u0430\u0442\u044b** - \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u043c\u0438 \u043a\u0430\u0440\u0442\u0430\u043c\u0438\r\n- \u2705 **\u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u044c** - \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u043f\u043e \u041e\u041a\u0422\u041c\u041e\r\n\r\n### \ud83d\udee1\ufe0f \u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\n- \u2705 **88% \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435 \u0442\u0435\u0441\u0442\u0430\u043c\u0438** - comprehensive test suite\r\n- \u2705 **\u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f mypy** - \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432\r\n- \u2705 **\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0435 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435** - \u043c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445\r\n- \u2705 **CI/CD pipeline** - \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430\r\n\r\n## \ud83d\udce6 \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\r\n\r\n### \u0418\u0437 PyPI (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f)\r\n\r\n```bash\r\npip install nalogo\r\n```\r\n\r\n### \u0414\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\ngit clone https://github.com/Rusik636/nalogo.git\r\ncd nalogo\r\npip install -e \".[dev]\"\r\n```\r\n\r\n## \ud83d\udd27 \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\r\n\r\n### \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\r\n\r\n```python\r\nimport asyncio\r\nfrom nalogo import Client\r\n\r\n# \u041f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\r\nclient = Client()\r\n\r\n# \u0421 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438\r\nclient = Client(\r\n    base_url=\"https://lknpd.nalog.ru/api\",  # \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 endpoint\r\n    storage_path=\"./tokens.json\",           # \u0424\u0430\u0439\u043b \u0434\u043b\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\n    device_id=\"my-device-123\"               # \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 ID \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\r\n)\r\n```\r\n\r\n### \ud83d\udd10 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n\r\n#### \u041f\u043e \u0418\u041d\u041d \u0438 \u043f\u0430\u0440\u043e\u043b\u044e\r\n\r\n```python\r\nasync def auth_with_inn():\r\n    client = Client()\r\n    \r\n    # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430\r\n    token = await client.create_new_access_token(\"123456789012\", \"your_password\")\r\n    \r\n    # \u0410\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n    await client.authenticate(token)\r\n    \r\n    print(\"\u2705 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430!\")\r\n    return client\r\n```\r\n\r\n#### \u041f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 (SMS)\r\n\r\n```python\r\nasync def auth_with_phone():\r\n    client = Client()\r\n    \r\n    # \u0428\u0430\u0433 1: \u0417\u0430\u043f\u0440\u043e\u0441 SMS \u043a\u043e\u0434\u0430\r\n    phone = \"79001234567\"\r\n    challenge = await client.create_phone_challenge(phone)\r\n    \r\n    print(f\"\ud83d\udcf1 SMS \u043a\u043e\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d. \u0422\u043e\u043a\u0435\u043d: {challenge['challengeToken']}\")\r\n    \r\n    # \u0428\u0430\u0433 2: \u0412\u0432\u043e\u0434 SMS \u043a\u043e\u0434\u0430 (\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f)\r\n    sms_code = input(\"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 SMS \u043a\u043e\u0434: \")\r\n    \r\n    # \u0428\u0430\u0433 3: \u0412\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430\r\n    token = await client.create_new_access_token_by_phone(\r\n        phone, challenge['challengeToken'], sms_code\r\n    )\r\n    \r\n    # \u0428\u0430\u0433 4: \u0410\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n    await client.authenticate(token)\r\n    \r\n    print(\"\u2705 SMS \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430!\")\r\n    return client\r\n```\r\n\r\n### \ud83d\udcb0 \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u043e\u0432\r\n\r\n#### \u041f\u0440\u043e\u0441\u0442\u043e\u0439 \u0447\u0435\u043a\r\n\r\n```python\r\nasync def create_simple_receipt():\r\n    client = await auth_with_inn()  # \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e\r\n    \r\n    income_api = client.income()\r\n    \r\n    result = await income_api.create(\r\n        name=\"\u041a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0443\u0441\u043b\u0443\u0433\u0438\",\r\n        amount=5000.00,  # \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 Decimal\r\n        quantity=1\r\n    )\r\n    \r\n    receipt_uuid = result[\"approvedReceiptUuid\"]\r\n    print(f\"\u2705 \u0427\u0435\u043a \u0441\u043e\u0437\u0434\u0430\u043d: {receipt_uuid}\")\r\n    \r\n    return receipt_uuid\r\n```\r\n\r\n#### \u0427\u0435\u043a \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u043c\u0438\r\n\r\n```python\r\nfrom nalogo.dto.income import IncomeServiceItem\r\nfrom decimal import Decimal\r\n\r\nasync def create_multi_item_receipt():\r\n    client = await auth_with_inn()\r\n    income_api = client.income()\r\n    \r\n    # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u0437\u0438\u0446\u0438\u0438\r\n    services = [\r\n        IncomeServiceItem(\r\n            name=\"\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442\u0430\",\r\n            amount=Decimal(\"50000.00\"),\r\n            quantity=Decimal(\"1\")\r\n        ),\r\n        IncomeServiceItem(\r\n            name=\"\u0422\u0435\u0445\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\",\r\n            amount=Decimal(\"5000.00\"),\r\n            quantity=Decimal(\"3\")  # 3 \u043c\u0435\u0441\u044f\u0446\u0430\r\n        )\r\n    ]\r\n    \r\n    result = await income_api.create_multiple_items(services)\r\n    \r\n    # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043e\u0431\u0449\u0443\u044e \u0441\u0443\u043c\u043c\u0443: 50000 + (5000 * 3) = 65000\r\n    total = sum(item.amount * item.quantity for item in services)\r\n    print(f\"\ud83d\udcb0 \u041e\u0431\u0449\u0430\u044f \u0441\u0443\u043c\u043c\u0430: {total}\")\r\n    \r\n    return result[\"approvedReceiptUuid\"]\r\n```\r\n\r\n#### \u0427\u0435\u043a \u0434\u043b\u044f \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043b\u0438\u0446\u0430\r\n\r\n```python\r\nfrom nalogo.dto.income import IncomeClient, IncomeType\r\n\r\nasync def create_legal_entity_receipt():\r\n    client = await auth_with_inn()\r\n    income_api = client.income()\r\n    \r\n    # \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u043c \u043b\u0438\u0446\u0435\r\n    legal_client = IncomeClient(\r\n        contact_phone=\"+79001234567\",\r\n        display_name=\"\u041e\u041e\u041e '\u0418\u043d\u043d\u043e\u0432\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f'\",\r\n        income_type=IncomeType.FROM_LEGAL_ENTITY,\r\n        inn=\"1234567890\"  # \u0418\u041d\u041d \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438\r\n    )\r\n    \r\n    result = await income_api.create(\r\n        name=\"\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u041f\u041e \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443\",\r\n        amount=250000.00,\r\n        quantity=1,\r\n        client=legal_client\r\n    )\r\n    \r\n    print(f\"\ud83c\udfe2 \u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0447\u0435\u043a: {result['approvedReceiptUuid']}\")\r\n    return result\r\n```\r\n\r\n### \u274c \u041e\u0442\u043c\u0435\u043d\u0430 \u0447\u0435\u043a\u043e\u0432\r\n\r\n```python\r\nfrom nalogo.dto.income import CancelCommentType\r\n\r\nasync def cancel_receipt():\r\n    client = await auth_with_inn()\r\n    income_api = client.income()\r\n    \r\n    receipt_uuid = \"your-receipt-uuid\"\r\n    \r\n    result = await income_api.cancel(\r\n        receipt_uuid=receipt_uuid,\r\n        comment_type=CancelCommentType.INCORRECT_DATA,\r\n        request_time=datetime.now(timezone.utc)\r\n    )\r\n    \r\n    print(f\"\u274c \u0427\u0435\u043a \u043e\u0442\u043c\u0435\u043d\u0435\u043d: {result}\")\r\n```\r\n\r\n### \ud83e\uddfe \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0447\u0435\u043a\u043e\u0432\r\n\r\n```python\r\nasync def get_receipt_info():\r\n    client = await auth_with_inn()\r\n    receipt_api = client.receipt()\r\n    \r\n    receipt_uuid = \"your-receipt-uuid\"\r\n    \r\n    # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 JSON \u0434\u0430\u043d\u043d\u044b\u0445\r\n    receipt_data = await receipt_api.json(receipt_uuid)\r\n    print(f\"\ud83d\udccb \u0421\u0443\u043c\u043c\u0430: {receipt_data.get('totalAmount')}\")\r\n    print(f\"\ud83d\udcc5 \u0414\u0430\u0442\u0430: {receipt_data.get('operationTime')}\")\r\n    \r\n    # \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f URL \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438\r\n    print_url = receipt_api.print_url(receipt_uuid)\r\n    print(f\"\ud83d\udda8\ufe0f \u041f\u0435\u0447\u0430\u0442\u044c: {print_url}\")\r\n```\r\n\r\n### \ud83d\udcca \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 API\r\n\r\n#### \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\r\n\r\n```python\r\nasync def get_user_info():\r\n    client = await auth_with_inn()\r\n    user_api = client.user()\r\n    \r\n    user_data = await user_api.get()\r\n    \r\n    print(f\"\ud83d\udc64 \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c: {user_data['displayName']}\")\r\n    print(f\"\ud83d\udccb \u0418\u041d\u041d: {user_data['inn']}\")\r\n    print(f\"\ud83d\udce7 Email: {user_data.get('email', '\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d')}\")\r\n    print(f\"\ud83d\udcf1 \u0422\u0435\u043b\u0435\u0444\u043e\u043d: {user_data['phone']}\")\r\n```\r\n\r\n#### \u0421\u043f\u043e\u0441\u043e\u0431\u044b \u043e\u043f\u043b\u0430\u0442\u044b\r\n\r\n```python\r\nasync def manage_payment_types():\r\n    client = await auth_with_inn()\r\n    payment_api = client.payment_type()\r\n    \r\n    # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u043e\u043f\u043b\u0430\u0442\u044b\r\n    payment_types = await payment_api.table()\r\n    print(f\"\ud83d\udcb3 \u041d\u0430\u0439\u0434\u0435\u043d\u043e {len(payment_types)} \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u043e\u043f\u043b\u0430\u0442\u044b\")\r\n    \r\n    # \u041f\u043e\u0438\u0441\u043a \u0438\u0437\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430\r\n    favorite = await payment_api.favorite()\r\n    if favorite:\r\n        print(f\"\u2b50 \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439: {favorite['bankName']}\")\r\n    else:\r\n        print(\"\u2b50 \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\")\r\n```\r\n\r\n#### \u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\r\n\r\n```python\r\nasync def get_tax_info():\r\n    client = await auth_with_inn()\r\n    tax_api = client.tax()\r\n    \r\n    # \u0422\u0435\u043a\u0443\u0449\u0438\u0435 \u043d\u0430\u043b\u043e\u0433\u0438\r\n    tax_data = await tax_api.get()\r\n    print(\"\ud83d\udcca \u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n    \r\n    # \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u043e \u041e\u041a\u0422\u041c\u041e\r\n    history = await tax_api.history(oktmo=\"12345678\")\r\n    print(f\"\ud83d\udcc8 \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n    \r\n    # \u041f\u043b\u0430\u0442\u0435\u0436\u0438 (\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043f\u043b\u0430\u0447\u0435\u043d\u043d\u044b\u0435)\r\n    payments = await tax_api.payments(oktmo=\"12345678\", only_paid=True)\r\n    print(f\"\ud83d\udcb8 \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n```\r\n\r\n## \ud83d\udee1\ufe0f \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\n\r\n### \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\n\r\n```python\r\n# \u274c \u041d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e - \u0442\u043e\u043a\u0435\u043d\u044b \u0432 \u043f\u0430\u043c\u044f\u0442\u0438\r\nclient = Client()\r\n\r\n# \u2705 \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f - \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432 \u0444\u0430\u0439\u043b\r\nclient = Client(storage_path=\"./secure_tokens.json\")\r\n\r\n# \u2705 \u041f\u0440\u043e\u0434\u0430\u043a\u0448\u043d - \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\nimport os\r\nfrom pathlib import Path\r\n\r\ntoken_path = Path(os.getenv(\"TOKEN_STORAGE_PATH\", \"./tokens.json\"))\r\nclient = Client(storage_path=str(token_path))\r\n```\r\n\r\n### \u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\n\r\n```python\r\nimport logging\r\n\r\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438\r\nlogging.basicConfig(level=logging.INFO)\r\nlogger = logging.getLogger(\"nalogo\")\r\n\r\n# \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u0430\u0441\u043a\u0438\u0440\u0443\u0435\u0442 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435:\r\n# - \u0422\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430\r\n# - \u041f\u0430\u0440\u043e\u043b\u0438\r\n# - \u041d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432\r\n# - \u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435\r\n```\r\n\r\n### \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a\r\n\r\n```python\r\nfrom nalogo.exceptions import (\r\n    UnauthorizedException,\r\n    ValidationException,\r\n    PhoneException,\r\n    DomainException\r\n)\r\n\r\nasync def safe_operation():\r\n    try:\r\n        client = Client()\r\n        token = await client.create_new_access_token(\"inn\", \"password\")\r\n        await client.authenticate(token)\r\n        \r\n    except UnauthorizedException:\r\n        print(\"\u274c \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0418\u041d\u041d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c\")\r\n    except ValidationException as e:\r\n        print(f\"\u274c \u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438: {e}\")\r\n    except PhoneException as e:\r\n        print(f\"\ud83d\udcf1 \u041e\u0448\u0438\u0431\u043a\u0430 SMS: {e}\")\r\n    except DomainException as e:\r\n        print(f\"\ud83d\udea8 API \u043e\u0448\u0438\u0431\u043a\u0430: {e}\")\r\n        # e.response \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 httpx.Response \u0434\u043b\u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430\r\n```\r\n\r\n## \u2699\ufe0f \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\r\n\r\n### \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\n\r\n\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b `.env`:\r\n\r\n```env\r\n# API \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\r\nNALOG_BASE_URL=https://lknpd.nalog.ru/api\r\nNALOG_DEVICE_ID=my-unique-device-id\r\n\r\n# \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\nTOKEN_STORAGE_PATH=./secure/tokens.json\r\n\r\n# \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\nNALOG_INN=123456789012\r\nNALOG_PASSWORD=your_secure_password\r\n```\r\n\r\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:\r\n\r\n```python\r\nimport os\r\nfrom dotenv import load_dotenv\r\n\r\nload_dotenv()\r\n\r\nclient = Client(\r\n    base_url=os.getenv(\"NALOG_BASE_URL\"),\r\n    device_id=os.getenv(\"NALOG_DEVICE_ID\"),\r\n    storage_path=os.getenv(\"TOKEN_STORAGE_PATH\")\r\n)\r\n```\r\n\r\n### \u041a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044f HTTP \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n\r\n```python\r\nfrom nalogo import Client\r\nfrom nalogo._http import AsyncHTTPClient\r\n\r\n# \u041a\u043b\u0438\u0435\u043d\u0442 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438\r\nclass CustomHTTPClient(AsyncHTTPClient):\r\n    def __init__(self, *args, **kwargs):\r\n        super().__init__(*args, **kwargs)\r\n        # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0430\u0443\u0442\r\n        self._client.timeout = 60.0\r\n        \r\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\r\nclient = Client()\r\nclient.http_client = CustomHTTPClient(\"https://lknpd.nalog.ru/api\")\r\n```\r\n\r\n## \ud83e\uddea \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\n\r\n### \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\npip install -e \".[dev]\"\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\r\n\r\n```bash\r\n# \u0412\u0441\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest\r\n\r\n# \u0421 \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435\u043c\r\npytest --cov=nalogo --cov-report=html\r\n\r\n# \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest tests/test_auth_async.py -v\r\n\r\n# \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest tests/test_income_async.py::TestIncomeAPI::test_create_success -v\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432\r\n\r\n```bash\r\n# \u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\r\npython examples/async_example.py\r\n\r\n# \u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0441 \u043c\u043e\u043a\u0430\u043c\u0438\r\npython demo.py\r\n```\r\n\r\n## \ud83d\udd04 \u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0441 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438\r\n\r\n### \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 API\r\n\r\n| PHP | Python | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 |\r\n|-----|--------|----------|\r\n| `$client->createNewAccessToken()` | `await client.create_new_access_token()` | \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e \u0418\u041d\u041d |\r\n| `$client->income()->create()` | `await client.income().create()` | \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u0430 |\r\n| `$client->receipt()->printUrl()` | `client.receipt().print_url()` | URL \u043f\u0435\u0447\u0430\u0442\u0438 |\r\n| `$paymentTypes->favorite()` | `await client.payment_type().favorite()` | \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043e\u043f\u043b\u0430\u0442\u044b |\r\n\r\n### \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f\r\n\r\n#### 1. \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c\r\n```php\r\n// PHP - \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043e\u0434\r\n$result = $client->income()->create($name, $amount, $quantity);\r\n```\r\n\r\n```python\r\n# Python - \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043e\u0434\r\nresult = await client.income().create(name, amount, quantity)\r\n```\r\n\r\n#### 2. \u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\n```php\r\n// PHP - \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\n$amount = \"100.50\"; // \u0421\u0442\u0440\u043e\u043a\u0430\r\n$quantity = 2; // \u0427\u0438\u0441\u043b\u043e\r\n```\r\n\r\n```python\r\n# Python - \u0441\u0442\u0440\u043e\u0433\u0430\u044f \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\nfrom decimal import Decimal\r\n\r\namount = Decimal(\"100.50\")  # Decimal \u0434\u043b\u044f \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438\r\nquantity = Decimal(\"2\")     # Decimal \u0434\u043b\u044f \u043a\u043e\u043d\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438\r\n```\r\n\r\n#### 3. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a\r\n```php\r\n// PHP - \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430\r\ntry {\r\n    $result = $client->income()->create(...);\r\n} catch (DomainException $e) {\r\n    // \u041e\u0431\u0449\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\r\n}\r\n```\r\n\r\n```python\r\n# Python - \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\r\ntry:\r\n    result = await client.income().create(...)\r\nexcept ValidationException as e:\r\n    # \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438\r\nexcept UnauthorizedException as e:\r\n    # \u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\r\n```\r\n\r\n### \u0428\u0430\u0431\u043b\u043e\u043d \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438\r\n\r\n```python\r\n# \u0428\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 PHP \u043a\u043e\u0434\u0430\r\nasync def migrate_from_php():\r\n    # 1. \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439\r\n    # PHP: $client = new ApiClient();\r\n    client = Client()\r\n    \r\n    # 2. \u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 await \u043a\u043e \u0432\u0441\u0435\u043c API \u0432\u044b\u0437\u043e\u0432\u0430\u043c\r\n    # PHP: $token = $client->createNewAccessToken($inn, $password);\r\n    token = await client.create_new_access_token(inn, password)\r\n    \r\n    # 3. \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043c\u0430\u0441\u0441\u0438\u0432\u044b \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u044b DTO\r\n    # PHP: $client = ['contactPhone' => $phone, ...];\r\n    from nalogo.dto.income import IncomeClient\r\n    client_data = IncomeClient(contact_phone=phone, ...)\r\n    \r\n    # 4. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 Decimal \u0434\u043b\u044f \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439\r\n    # PHP: $amount = 100.50;\r\n    from decimal import Decimal\r\n    amount = Decimal(\"100.50\")\r\n    \r\n    # 5. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\r\n    # PHP: catch (DomainException $e)\r\n    # Python: except DomainException as e\r\n```\r\n\r\n## \ud83d\udcca \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\r\n\r\n### \u0411\u0435\u043d\u0447\u043c\u0430\u0440\u043a\u0438\r\n\r\n| \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f | PHP (sync) | Python (async) | \u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 |\r\n|----------|------------|----------------|-----------|\r\n| \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f | ~2.1s | ~0.8s | 2.6x |\r\n| \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u0430 | ~1.5s | ~0.6s | 2.5x |\r\n| 10 \u0447\u0435\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e | ~15s | ~6s | 2.5x |\r\n| 10 \u0447\u0435\u043a\u043e\u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e | ~15s | ~2s | 7.5x |\r\n\r\n### \u041e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u0441\u043e\u043a\u0438\u0445 \u043d\u0430\u0433\u0440\u0443\u0437\u043e\u043a\r\n\r\n```python\r\nimport asyncio\r\nfrom nalogo import Client\r\n\r\nasync def bulk_receipts():\r\n    client = await auth_with_inn()\r\n    income_api = client.income()\r\n    \r\n    # \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0447\u0435\u043a\u043e\u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\r\n    tasks = []\r\n    for i in range(100):\r\n        task = income_api.create(f\"\u0423\u0441\u043b\u0443\u0433\u0430 {i}\", 1000.00, 1)\r\n        tasks.append(task)\r\n    \r\n    # \u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0437\u0430\u0434\u0430\u0447 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\r\n    results = await asyncio.gather(*tasks, return_exceptions=True)\r\n    \r\n    success_count = sum(1 for r in results if not isinstance(r, Exception))\r\n    print(f\"\u2705 \u0421\u043e\u0437\u0434\u0430\u043d\u043e {success_count} \u0438\u0437 {len(tasks)} \u0447\u0435\u043a\u043e\u0432\")\r\n```\r\n\r\n## \u26a0\ufe0f \u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\r\n\r\n### API \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\r\n- **Invoice API** \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d (\u043f\u043e\u043c\u0435\u0447\u0435\u043d \u043a\u0430\u043a \"Not implemented\" \u0432 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435)\r\n- **API \u0432\u0435\u0440\u0441\u0438\u0438** v1/v2 endpoints \u043c\u043e\u0433\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0432 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0438\r\n- **\u041b\u0438\u043c\u0438\u0442\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432** \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c \u041c\u043e\u0439 \u041d\u0430\u043b\u043e\u0433\r\n\r\n### \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c\r\n- **Python 3.11+** \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d \u0434\u043b\u044f \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e async \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430\r\n- **Pydantic v2** \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438\r\n- **httpx** \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u0441\u0438\u044f 0.25.0+\r\n\r\n## \ud83e\udd1d \u0412\u043a\u043b\u0430\u0434 \u0432 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435\r\n\r\n### \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\n# \u041a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\ngit clone https://github.com/Rusik636/nalogo.git\r\ncd nalogo\r\n\r\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\npython -m venv .venv\r\nsource .venv/bin/activate  # Linux/Mac\r\n# \u0438\u043b\u0438\r\n.venv\\Scripts\\activate     # Windows\r\n\r\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\npip install -e \".[dev]\"\r\n\r\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 pre-commit \u0445\u0443\u043a\u043e\u0432\r\npre-commit install\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430\r\n\r\n```bash\r\n# \u041b\u0438\u043d\u0442\u0438\u043d\u0433\r\nruff check .\r\n\r\n# \u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\nblack .\r\n\r\n# \u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\nmypy nalogo/\r\n\r\n# \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\nbandit -r nalogo/\r\n\r\n# \u041f\u043e\u043b\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 (\u043a\u0430\u043a \u0432 CI)\r\npytest --cov=nalogo --cov-fail-under=80\r\n```\r\n\r\n### \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 PR\r\n\r\n1. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0432\u0435\u0442\u043a\u0443 \u0434\u043b\u044f \u0444\u0438\u0447\u0438: `git checkout -b feature/amazing-feature`\r\n2. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438\r\n3. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c \u0447\u0442\u043e \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442\r\n4. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 PR \u0441 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\r\n\r\n## \ud83d\udcc4 \u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f\r\n\r\nMIT License - \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435 [LICENSE](LICENSE).\r\n\r\n## \ud83d\ude4f \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u0438\r\n\r\n- **Artem Dubinin** ([shoman4eg](https://github.com/shoman4eg)) - \u0430\u0432\u0442\u043e\u0440 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438\r\n- **\u041a\u043e\u043c\u0430\u043d\u0434\u0430 httpx** - \u0437\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0439 async HTTP \u043a\u043b\u0438\u0435\u043d\u0442\r\n- **\u041a\u043e\u043c\u0430\u043d\u0434\u0430 Pydantic** - \u0437\u0430 \u043c\u043e\u0449\u043d\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0434\u0430\u043d\u043d\u044b\u0445\r\n- **\u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e Python** - \u0437\u0430 async/await \u0438 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b\r\n\r\n## \ud83d\udcde \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\r\n\r\n- \ud83d\udccb **Issues**: [GitHub Issues](https://github.com/Rusik636/nalogo/issues)\r\n- \ud83d\udcd6 **\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f**: [README.md](README.md)\r\n- \ud83d\udcac **\u041e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u044f**: [GitHub Discussions](https://github.com/Rusik636/nalogo/discussions)\r\n- \ud83d\udce7 **Email**: ruslan.prokshin@mail.ru\r\n\r\n---\r\n\r\n**\u0421\u0434\u0435\u043b\u0430\u043d\u043e \u0441 \u2764\ufe0f \u0434\u043b\u044f Python-\u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430**\r\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Asynchronous Python client for Russian self-employed tax service API (Moy Nalog / lknpd.nalog.ru)",
    "version": "1.0.0",
    "project_urls": {
        "Bug Reports": "https://github.com/your-org/nalogo/issues",
        "Changelog": "https://github.com/your-org/nalogo/blob/main/CHANGELOG.md",
        "Documentation": "https://github.com/your-org/nalogo#readme",
        "Homepage": "https://github.com/your-org/nalogo",
        "Original PHP Library": "https://github.com/shoman4eg/moy-nalog",
        "Repository": "https://github.com/your-org/nalogo"
    },
    "split_keywords": [
        "nalog",
        " tax",
        " self-employed",
        " api",
        " async",
        " russia",
        " moy-nalog",
        " freelancer",
        " invoice",
        " receipt",
        " lknpd",
        " \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0439"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "046debb0e0320437bce0c284b5802236fd96a412feb690b972a870624e1a880a",
                "md5": "4269ddb5b1f260f6152481b4ebdea4cd",
                "sha256": "4ca1d67521bb37fe9aee33102a6b79af5e2a155e7cb8f96765815b5d42946320"
            },
            "downloads": -1,
            "filename": "nalogo-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4269ddb5b1f260f6152481b4ebdea4cd",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 30873,
            "upload_time": "2025-08-15T21:07:06",
            "upload_time_iso_8601": "2025-08-15T21:07:06.569857Z",
            "url": "https://files.pythonhosted.org/packages/04/6d/ebb0e0320437bce0c284b5802236fd96a412feb690b972a870624e1a880a/nalogo-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "e784dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc",
                "md5": "0fcbc909a7ad857e1ef110f33523dbe7",
                "sha256": "e53edbf4b498928a1a840cf8f6adad28d47e1b98f5d95bf9f7723c796f0a3c60"
            },
            "downloads": -1,
            "filename": "nalogo-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "0fcbc909a7ad857e1ef110f33523dbe7",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 37332,
            "upload_time": "2025-08-15T21:07:08",
            "upload_time_iso_8601": "2025-08-15T21:07:08.288604Z",
            "url": "https://files.pythonhosted.org/packages/e7/84/dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc/nalogo-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-15 21:07:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "your-org",
    "github_project": "nalogo",
    "github_not_found": true,
    "lcname": "nalogo"
}
        
Elapsed time: 0.50820s