invoice_az/PROJECT_OVERVIEW.md

1975 lines
89 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Обзор проекта Invoice Az
## Оглавление
1. [Общая информация](#общая-информация)
2. [Архитектура проекта](#архитектура-проекта)
3. [Технологический стек](#технологический-стек)
4. [Структура файлов](#структура-файлов)
5. [Основные модули](#основные-модули)
6. [DocTypes](#doctypes)
7. [API интеграция](#api-интеграция)
8. [Рабочие процессы](#рабочие-процессы)
9. [Установка и настройка](#установка-и-настройка)
10. [Разработка](#разработка)
---
## Общая информация
**Название проекта:** Invoice Az (Invoices Azərbaycan)
**Версия:** 0.0.1
**Автор:** Jey ERP (info@jeyerp.az)
**Лицензия:** Unlicense (Общественное достояние)
**Требования:** Python 3.10+
### Назначение
Invoice Az - это приложение интеграции для Frappe/ERPNext, которое обеспечивает двустороннюю синхронизацию с государственной системой электронного налогообложения Азербайджана E-Taxes (https://new.e-taxes.gov.az).
### Основные возможности
- **Импорт счетов-фактур** - автоматический импорт входящих счетов от поставщиков
- **Экспорт счетов-фактур** - отправка исходящих счетов клиентам через E-Taxes
- **Управление справочниками** - синхронизация товаров, единиц измерения, контрагентов
- **Интеллектуальное сопоставление** - автоматическое сопоставление данных между системами с точностью 95%
- **ASAN аутентификация** - интеграция с государственной системой аутентификации ASAN Login
- **Массовые операции** - обработка до 200 счетов за один запрос
- **Отслеживание и аудит** - полная история операций и связей между документами
---
## Архитектура проекта
### Структура директорий
```
/home/frappe/frappe-bench/apps/invoice_az/
├── README.md # Краткая документация
├── claude.md # Подробная документация на русском (34 КБ)
├── QA_SEND_INVOICE.md # Q&A по отправке счетов
├── pyproject.toml # Конфигурация Python проекта
├── license.txt # Лицензия
├── invoice_az/ # Основная директория приложения
│ ├── api.py # API для закупок (4,320 строк)
│ ├── auth.py # ASAN аутентификация (968 строк)
│ ├── sales_api.py # API для продаж (887 строк)
│ ├── send_sales_api.py # Отправка счетов (730 строк)
│ ├── hooks.py # Интеграция с Frappe (292 строк)
│ ├── client/ # JavaScript клиентские скрипты
│ │ ├── etaxes_common.js # Общие утилиты (1,052 строки)
│ │ ├── purchase_order.js # Заказы на покупку (1,532 строки)
│ │ ├── sales_order.js # Заказы на продажу (1,585 строк)
│ │ ├── sales_invoice.js # Счета на оплату (1,856 строк)
│ │ └── [списки для справочников] # 4 файла по ~900 строк
│ └── invoice_az/ # Определения DocTypes
│ └── doctype/ # 17 кастомных DocTypes
│ ├── asan_login/ # Хранение токенов ASAN
│ ├── e_taxes_settings/ # Настройки E-Taxes
│ ├── e_taxes_purchase/ # Входящие счета
│ ├── e_taxes_sales/ # Исходящие счета
│ ├── e_taxes_item/ # Товары из E-Taxes
│ ├── e_taxes_unit/ # Единицы измерения
│ ├── e_taxes_customers/ # Клиенты
│ ├── e_taxes_suppliers/ # Поставщики
│ └── [mapping doctypes] # Таблицы сопоставления
```
### Уровни приложения
1. **Уровень представления (Frontend)**
- JavaScript клиентские скрипты
- Интеграция с Frappe UI
- Диалоги и формы
- Списки с возможностью массовых операций
2. **Уровень бизнес-логики (Backend)**
- Python модули API
- Обработка данных E-Taxes
- Интеллектуальное сопоставление
- Создание документов в ERPNext
3. **Уровень данных (Database)**
- 17 кастомных DocTypes
- Связи с стандартными DocTypes ERPNext
- Индексированные поля для быстрого поиска
4. **Внешние интеграции**
- REST API E-Taxes
- ASAN Login аутентификация
- Планировщик задач Frappe
---
## Технологический стек
### Backend
| Технология | Назначение |
|-----------|-----------|
| **Python 3.10+** | Основной язык программирования |
| **Frappe Framework** | Веб-фреймворк для бизнес-приложений |
| **Frappe ORM** | Работа с базой данных |
| **requests** | HTTP клиент для API |
| **difflib.SequenceMatcher** | Нечеткое сопоставление (95%) |
| **re (regex)** | Обработка строк и валидация |
| **json** | Сериализация/десериализация данных |
### Frontend
| Технология | Назначение |
|-----------|-----------|
| **JavaScript ES6+** | Клиентские скрипты |
| **jQuery** | Манипуляция DOM |
| **Frappe UI** | Готовые UI компоненты |
| **frappe.call()** | AJAX коммуникация |
### Database
| Технология | Назначение |
|-----------|-----------|
| **MariaDB** | Реляционная СУБД |
| **InnoDB** | Storage engine для всех DocTypes |
| **Индексы** | etaxes_item_name, etaxes_unit_name, etaxes_party_name |
### Внешние API
- **E-Taxes REST API** (https://new.e-taxes.gov.az)
- Аутентификация: Bearer Token через ASAN Login
- Формат: JSON
- Протокол: HTTPS
- Ограничение: 100мс между запросами
### Инструменты разработки
- **ruff** - Линтинг и форматирование Python
- **eslint** - Линтинг JavaScript
- **prettier** - Форматирование кода
- **pyupgrade** - Модернизация синтаксиса Python
- **pre-commit** - Git хуки для качества кода
---
## Структура файлов
### Статистика кода
| Категория | Количество | Строки кода |
|-----------|-----------|-------------|
| **Python модули** | 5 основных | 7,198 строк |
| **JavaScript файлы** | 9 файлов | 9,786 строк |
| **DocTypes** | 17 кастомных | ~80 файлов |
| **Документация** | 3 MD файла | 50 КБ |
| **Конфигурация** | 5 файлов | ~2.5 КБ |
### Основные конфигурационные файлы
**pyproject.toml** - Метаданные проекта, зависимости, настройки ruff/eslint
```toml
[project]
name = "invoice_az"
version = "0.0.1"
requires-python = ">=3.10"
[tool.ruff]
line-length = 110
target-version = "py310"
```
**hooks.py** - Точки интеграции с Frappe
- Обработчики событий DocType
- Задачи планировщика (каждые 4 минуты)
- Клиентские скрипты
- Хуки жизненного цикла
**.editorconfig** - Настройки редактора
- Отступы, окончания строк
- Кодировка UTF-8
**.gitignore** - Правила исключения Git
- Байт-код Python
- node_modules
- Логи и кэш
---
## Основные модули
### 1. api.py (4,320 строк)
**Назначение:** Основной модуль для работы с входящими счетами-фактурами
**Ключевые функции:**
```python
@frappe.whitelist()
def load_items_from_invoices(from_date, to_date, page, company, page_size=200)
# Загрузка счетов из E-Taxes за период
# Пагинация, кэширование (5 минут)
# Возвращает список счетов с деталями
@frappe.whitelist()
def create_purchase_order(invoice_data, company, set_warehouse=None, supplier=None, update_rate=True, expand_desc=True)
# Создание Purchase Order из счета E-Taxes
# Интеллектуальное сопоставление товаров/поставщиков
# Автоматическое создание недостающих записей
def load_items()
# Синхронизация справочника товаров из E-Taxes
# Массовая загрузка до 200 записей
def load_units()
# Синхронизация единиц измерения
def load_parties()
# Синхронизация контрагентов (клиенты/поставщики)
```
**Интеллектуальное сопоставление:**
```python
def find_best_match(etaxes_name, existing_names):
# Использует difflib.SequenceMatcher
# Порог совпадения: 95%
# Поддержка азербайджанских символов (ə, ö, ü, ğ, ş, ç, ı)
# Автоматическое сопоставление при высокой схожести
```
### 2. auth.py (968 строк)
**Назначение:** Аутентификация через ASAN Login и управление токенами
**Этапы аутентификации:**
1. **Инициализация входа**
```python
@frappe.whitelist()
def initiate_asan_login()
# Получение session_id от E-Taxes
# Возврат QR кода для мобильного приложения ASAN
```
2. **Проверка статуса**
```python
@frappe.whitelist()
def check_asan_login_status(session_id, docname)
# Опрос статуса каждые 2 секунды
# Обработка подтверждения через мобильное приложение
```
3. **Выбор сертификата**
```python
@frappe.whitelist()
def select_certificate(session_id, cert_id, docname)
# Получение access_token
```
4. **Выбор налогоплательщика**
```python
@frappe.whitelist()
def select_taxpayer(taxpayer_id, docname)
# Получение main_token для работы с E-Taxes API
# Сохранение в Asan Login doctype
```
**Автоматическое обновление токена:**
```python
def renew_token_background()
# Cron задача: каждые 4 минуты
# Пропускает, если нет активности
# Обрабатывает ошибки 401/500
# Устанавливает auth_status при неудаче
```
### 3. sales_api.py (887 строк)
**Назначение:** Обработка исходящих счетов-фактур
**Основные функции:**
```python
@frappe.whitelist()
def load_sales_invoices_from_etaxes(from_date, to_date, page, company, page_size=200)
# Загрузка исходящих счетов из E-Taxes
# Структура аналогична load_items_from_invoices
@frappe.whitelist()
def create_sales_order(invoice_data, company, set_warehouse=None, customer=None, update_rate=True, expand_desc=True)
# Создание Sales Order из счета E-Taxes
# Сопоставление клиентов и товаров
# Автоматическое создание недостающих записей
```
### 4. send_sales_api.py (730 строк)
**Назначение:** Отправка счетов в E-Taxes
**Процесс отправки:**
```python
@frappe.whitelist()
def send_sales_invoice_to_etaxes(sales_invoice_name)
# 1. Валидация данных
# - Проверка customer.tax_id (ИНН)
# - Проверка item.product_group_code
# - Защита от дубликатов
# 2. Генерация серийного номера
serial_number = generate_serial_number()
# 3. Формирование JSON payload
invoice_json = {
"receiver": {"name": customer_name, "tin": tin},
"invoiceLines": [
{
"productName": item_name,
"quantity": qty,
"pricePerUnit": rate,
"vatRate": vat_rate,
"vatAmount": vat_amount
}
],
"comment": comments
}
# 4. Отправка в E-Taxes
response = submit_invoice(invoice_json)
# 5. Автоматическая подпись через ASAN
sign_invoice_with_asan(invoice_id)
# 6. Сохранение результата
create_etaxes_sales_outbox_record()
```
### 5. hooks.py (292 строки)
**Назначение:** Интеграция с Frappe Framework
**Обработчики событий:**
```python
doc_events = {
"Purchase Order": {
"on_trash": "invoice_az.api.unlink_etaxes_record",
"on_cancel": "invoice_az.api.unlink_etaxes_record",
},
"Sales Order": {
"on_trash": "invoice_az.sales_api.unlink_sales_etaxes_record",
"on_cancel": "invoice_az.sales_api.unlink_sales_etaxes_record",
}
}
```
**Планировщик задач:**
```python
scheduler_events = {
"cron": {
"*/4 * * * *": [ # Каждые 4 минуты
"invoice_az.auth.renew_token_background"
]
}
}
```
**Клиентские скрипты:**
```python
doctype_js = {
"Purchase Order": "client/purchase_order.js",
"Sales Order": "client/sales_order.js",
"Sales Invoice": "client/sales_invoice.js",
"Purchase Invoice": "client/purchase_invoice.js"
}
```
---
## DocTypes
### Конфигурационные DocTypes (4)
#### 1. Asan Login
**Назначение:** Хранение токенов и данных аутентификации ASAN
**Основные поля:**
- `session_id` - ID сессии ASAN
- `access_token` - Токен доступа
- `main_token` - Основной токен для E-Taxes API
- `certificate_id` - ID выбранного сертификата
- `taxpayer_id` - ID налогоплательщика
- `taxpayer_name` - Название организации
- `auth_status` - Статус: Authenticated / Not Authenticated
- `last_activity` - Время последней активности
- `token_expiry` - Время истечения токена
**Методы:**
- `get_headers()` - Формирование заголовков для API запросов
#### 2. E-Taxes Settings
**Назначение:** Центральная конфигурация приложения
**Основные поля:**
- `api_base_url` - Базовый URL E-Taxes API
- `company` - Компания по умолчанию
- `default_warehouse` - Склад по умолчанию
- `item_mappings` - Таблица сопоставления товаров
- `unit_mappings` - Таблица сопоставления единиц
- `party_mappings` - Таблица сопоставления контрагентов
- `auto_create_items` - Автосоздание товаров
- `fuzzy_match_threshold` - Порог нечеткого совпадения (0.95)
#### 3. E-Taxes Purchase
**Назначение:** Отслеживание импортированных счетов закупок
**Основные поля:**
- `invoice_id` - ID счета в E-Taxes
- `invoice_serial_number` - Серийный номер
- `invoice_date` - Дата счета
- `supplier_name` - Название поставщика
- `supplier_tin` - ИНН поставщика
- `total_amount` - Общая сумма
- `vat_amount` - Сумма НДС
- `purchase_order` - Связь с Purchase Order
- `purchase_invoice` - Связь с Purchase Invoice
- `verification_code` - Код проверки
- `status` - Статус: New / Mapped / Imported
#### 4. E-Taxes Sales
**Назначение:** Отслеживание экспортированных счетов продаж
**Аналогичная структура E-Taxes Purchase, но для исходящих счетов**
### Справочные DocTypes (5)
#### 5. E-Taxes Item
**Назначение:** Товары/услуги из E-Taxes
**Основные поля:**
- `etaxes_item_code` - Код товара в E-Taxes
- `etaxes_item_name` - Название (индексировано)
- `category` - Категория
- `unit_code` - Код единицы измерения
- `mapped_item` - Связь с Item (ERPNext)
- `mapping_status` - Статус: Unmapped / Auto-Mapped / Manually Mapped
#### 6. E-Taxes Unit
**Назначение:** Единицы измерения из E-Taxes
**Основные поля:**
- `etaxes_unit_code` - Код единицы
- `etaxes_unit_name` - Название (индексировано)
- `mapped_uom` - Связь с UOM (ERPNext)
- `mapping_status` - Статус сопоставления
#### 7. E-Taxes Customers
**Назначение:** Клиенты из E-Taxes
**Основные поля:**
- `customer_tin` - ИНН клиента
- `customer_name` - Название
- `mapped_customer` - Связь с Customer (ERPNext)
- `mapping_status` - Статус сопоставления
#### 8. E-Taxes Suppliers
**Назначение:** Поставщики из E-Taxes
**Основные поля:**
- `supplier_tin` - ИНН поставщика
- `supplier_name` - Название
- `mapped_supplier` - Связь с Supplier (ERPNext)
- `mapping_status` - Статус сопоставления
#### 9. E-Taxes Parties
**Назначение:** Универсальные записи контрагентов
**Основные поля:**
- `party_tin` - ИНН контрагента
- `party_name` - Название
- `party_type` - Тип: Customer / Supplier / Both
### Таблицы сопоставления (5 Child Tables)
#### 10. E-Taxes Item Mapping
- `etaxes_item_code` - Код товара E-Taxes
- `erp_item_code` - Код товара ERPNext
- `mapping_type` - Тип: Auto / Manual
#### 11. E-Taxes Unit Mapping
- `etaxes_unit_code` - Код единицы E-Taxes
- `erp_uom` - UOM в ERPNext
#### 12. E-Taxes Customer Mappings
- `etaxes_customer_tin` - ИНН клиента E-Taxes
- `erp_customer` - Customer в ERPNext
#### 13. E-Taxes Supplier Mappings
- `etaxes_supplier_tin` - ИНН поставщика E-Taxes
- `erp_supplier` - Supplier в ERPNext
#### 14. E-Taxes Party Mapping
- `etaxes_party_tin` - ИНН контрагента E-Taxes
- `erp_party` - Контрагент в ERPNext
- `party_type` - Тип контрагента
### Отслеживание отправки (1)
#### 15. E-Taxes Sales Outbox
**Назначение:** Журнал отправленных счетов в E-Taxes
**Основные поля:**
- `sales_invoice` - Связь с Sales Invoice
- `etaxes_invoice_id` - ID счета в E-Taxes
- `serial_number` - Серийный номер
- `verification_code` - Код проверки
- `send_date` - Дата отправки
- `send_status` - Статус: Pending / Sent / Signed / Failed
- `error_message` - Сообщение об ошибке
### Тестовый DocType (1)
#### 16. TestAPI
**Назначение:** Разработка и отладка API вызовов
---
## API интеграция
### Эндпоинты E-Taxes
#### Аутентификация
**Инициализация входа**
```
POST /api/po/auth/public/v1/initiate
```
**Проверка статуса**
```
POST /api/po/auth/public/v1/status/{session_id}
```
**Выбор сертификата**
```
POST /api/po/auth/public/v1/certificate/select
Body: {"sessionId": "...", "certificateId": "..."}
```
**Выбор налогоплательщика**
```
POST /api/po/auth/public/v1/taxpayer/select
Body: {"accessToken": "...", "taxpayerId": "..."}
```
**Обновление токена**
```
POST /api/po/auth/public/v1/renew
Headers: {"x-authorization": "Bearer {main_token}"}
```
#### Работа со счетами
**Получение входящих счетов (inbox)**
```
POST /api/po/invoice/public/v2/invoice/find.inbox
Body: {
"from": "2024-01-01",
"to": "2024-12-31",
"page": 0,
"size": 200
}
```
**Получение исходящих счетов (outbox)**
```
POST /api/po/invoice/public/v2/invoice/find.outbox
Body: {
"from": "2024-01-01",
"to": "2024-12-31",
"page": 0,
"size": 200
}
```
**Детали счета**
```
GET /api/po/invoice/public/v2/invoice/{invoice_id}
```
**Создание счета**
```
POST /api/po/invoice/public/v2/invoice
Body: {
"receiver": {
"name": "ООО Компания",
"tin": "1234567890"
},
"invoiceLines": [
{
"productName": "Товар 1",
"quantity": 10,
"pricePerUnit": 100,
"vatRate": 18,
"vatAmount": 180
}
]
}
```
**Генерация серийного номера**
```
POST /api/po/invoice/public/v1/generateSerialNumber/defaultInvoice
```
**Подпись счета через ASAN**
```
POST /api/po/invoice/public/v1/invoice/sign/withAsanImza
Body: {"invoiceId": "..."}
```
### Заголовки запросов
Все запросы к E-Taxes API требуют следующих заголовков:
```python
headers = {
"Content-Type": "application/json",
"x-authorization": f"Bearer {main_token}",
"User-Agent": "Mozilla/5.0...",
"Accept": "application/json"
}
```
### Обработка ошибок
```python
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
# Токен истек - обновить токен
renew_token()
elif e.response.status_code == 500:
# Ошибка сервера - повторить с задержкой
time.sleep(1)
retry_request()
else:
frappe.log_error(str(e), "E-Taxes API Error")
```
### Ограничения API
- **Размер страницы:** максимум 200 записей
- **Задержка между запросами:** минимум 100мс
- **Время жизни токена:** обновление каждые 4 минуты
- **Кэширование:** 5 минут для списков счетов
---
## Рабочие процессы
### Процесс импорта счета закупки (Inbox)
```
┌─────────────────────────────────────────────────────────┐
│ 1. Пользователь открывает Purchase Order │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. Нажимает кнопку "Load from E-Taxes" │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 3. Выбирает диапазон дат (from_date, to_date) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 4. Frontend → load_items_from_invoices() │
│ - Проверка кэша (5 минут) │
│ - Если нет - запрос к E-Taxes API │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 5. E-Taxes API возвращает список счетов │
│ - Номер счета, дата │
│ - Поставщик (название, ИНН) │
│ - Список позиций (товар, кол-во, цена) │
│ - НДС │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 6. Отображается браузер счетов │
│ - Таблица со всеми счетами │
│ - Фильтрация, поиск │
│ - Просмотр позиций счета │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 7. Пользователь выбирает счет и нажимает "Import" │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 8. create_purchase_order(invoice_data) │
│ │
│ A. Сопоставление поставщика: │
│ - Поиск по ИНН в Supplier │
│ - Если нет - нечеткое сопоставление по имени │
│ - Если совпадение < 95% - создать нового │
│ │
│ B. Сопоставление товаров: │
│ ┌─ Для каждой позиции счета ─┐ │
│ │ │ │
│ │ 1. Поиск в Item Mapping │ │
│ │ 2. Нечеткое сопоставление │ │
│ │ 3. Если нет - создать Item │ │
│ └──────────────────────────────┘ │
│ │
│ C. Сопоставление единиц измерения: │
│ - Поиск в UOM Mapping │
│ - Если нет - создать UOM │
│ │
│ D. Создание Purchase Order: │
│ - Заполнение полей │
│ - Добавление позиций (items) │
│ - Расчет сумм и НДС │
│ - Сохранение документа │
│ │
│ E. Создание E-Taxes Purchase record: │
│ - Сохранение ID счета E-Taxes │
│ - Связь с Purchase Order │
│ - Статус: Imported │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 9. Purchase Order готов к отправке (Submit) │
└─────────────────────────────────────────────────────────┘
```
### Процесс экспорта счета продажи (Outbox)
```
┌─────────────────────────────────────────────────────────┐
│ 1. Пользователь создает Sales Invoice в ERPNext │
│ - Выбирает клиента (обязательно tax_id) │
│ - Добавляет позиции (обязательно product_group_code)│
│ - Заполняет количество, цену │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. Отправляет документ (Submit) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 3. Нажимает "Send to E-Taxes" │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 4. Показывается первое подтверждение │
│ "Вы уверены, что хотите отправить счет?" │
└─────────────────────┬───────────────────────────────────┘
│ Да
┌─────────────────────────────────────────────────────────┐
│ 5. Показывается второе подтверждение │
│ "Это действие необратимо. Продолжить?" │
└─────────────────────┬───────────────────────────────────┘
│ Да
┌─────────────────────────────────────────────────────────┐
│ 6. send_sales_invoice_to_etaxes() │
│ │
│ A. Валидация данных: │
│ - Проверка customer.tax_id (ИНН) │
│ - Проверка items.product_group_code │
│ - Проверка дубликатов в Outbox │
│ │
│ B. Генерация серийного номера: │
│ POST /generateSerialNumber/defaultInvoice │
│ Получение уникального номера для счета │
│ │
│ C. Формирование JSON payload: │
│ { │
│ "serialNumber": "...", │
│ "receiver": { │
│ "name": "ООО Клиент", │
│ "tin": "1234567890" │
│ }, │
│ "invoiceLines": [ │
│ { │
│ "productName": "Товар", │
│ "productGroupCode": "12345", │
│ "quantity": 10, │
│ "pricePerUnit": 100, │
│ "totalPrice": 1000, │
│ "vatRate": 18, │
│ "vatAmount": 180 │
│ } │
│ ], │
│ "totalAmount": 1180, │
│ "comment": "Примечание" │
│ } │
│ │
│ D. Отправка в E-Taxes: │
│ POST /api/po/invoice/public/v2/invoice │
│ Получение invoice_id │
│ │
│ E. Автоматическая подпись через ASAN: │
│ POST /invoice/sign/withAsanImza │
│ Body: {"invoiceId": "..."} │
│ │
│ F. Создание E-Taxes Sales Outbox record: │
│ - Сохранение invoice_id │
│ - Сохранение serial_number │
│ - Сохранение verification_code │
│ - Связь с Sales Invoice │
│ - Статус: Sent & Signed │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 7. Показывается сообщение об успехе │
│ "Счет успешно отправлен в E-Taxes" │
│ "Invoice ID: 12345" │
│ "Serial Number: ABC-123" │
└─────────────────────────────────────────────────────────┘
```
### Процесс аутентификации ASAN
```
┌─────────────────────────────────────────────────────────┐
│ 1. Пользователь открывает ASAN Login doctype │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. Нажимает "Login with ASAN" │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 3. initiate_asan_login() │
│ POST /api/po/auth/public/v1/initiate │
│ Получение session_id и qrCodeValue │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 4. Отображается QR код │
│ "Отсканируйте QR код в мобильном приложении ASAN" │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 5. Опрос статуса каждые 2 секунды │
│ check_asan_login_status(session_id) │
│ POST /api/po/auth/public/v1/status/{session_id} │
│ │
│ Статусы: │
│ - PENDING → продолжить опрос │
│ - APPROVED → переход к шагу 6 │
│ - REJECTED → показать ошибку │
└─────────────────────┬───────────────────────────────────┘
│ APPROVED
┌─────────────────────────────────────────────────────────┐
│ 6. Получение списка сертификатов │
│ Отображается диалог выбора сертификата │
│ - Название сертификата │
│ - Срок действия │
│ - Организация │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 7. Пользователь выбирает сертификат │
│ select_certificate(session_id, cert_id) │
│ POST /certificate/select │
│ Получение access_token │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 8. Получение списка налогоплательщиков │
│ Отображается диалог выбора налогоплательщика │
│ - Название организации │
│ - ИНН │
│ - Тип налогоплательщика │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 9. Пользователь выбирает налогоплательщика │
│ select_taxpayer(taxpayer_id) │
│ POST /taxpayer/select │
│ Получение main_token │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 10. Сохранение в ASAN Login doctype: │
│ - session_id │
│ - access_token │
│ - main_token ← используется для всех API запросов │
│ - certificate_id │
│ - taxpayer_id │
│ - taxpayer_name │
│ - auth_status = "Authenticated" │
│ - last_activity = now() │
│ - token_expiry = now() + 4 minutes │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 11. Аутентификация завершена │
│ "Вы успешно вошли как [taxpayer_name]" │
└─────────────────────────────────────────────────────────┘
```
### Процесс автоматического обновления токена
```
┌─────────────────────────────────────────────────────────┐
│ Cron задача: Каждые 4 минуты │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ renew_token_background() │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Проверка: Есть ли активные ASAN Login записи? │
└─────────────────────┬───────────────────────────────────┘
│ Нет → Завершить
│ Да ↓
┌─────────────────────────────────────────────────────────┐
│ Проверка: Была ли активность за последние 10 минут? │
└─────────────────────┬───────────────────────────────────┘
│ Нет → Завершить (экономия ресурсов)
│ Да ↓
┌─────────────────────────────────────────────────────────┐
│ Отправка запроса на обновление токена │
│ POST /api/po/auth/public/v1/renew │
│ Headers: {"x-authorization": "Bearer {main_token}"} │
└─────────────────────┬───────────────────────────────────┘
├─ Успех (200) ─────────────────────┐
│ │
│ ┌──────────────────────────────┐ │
│ │ Получение нового main_token │ │
│ │ Обновление ASAN Login record │ │
│ │ last_activity = now() │ │
│ │ token_expiry = now() + 4min │ │
│ └──────────────────────────────┘ │
│ │
├─ Ошибка 401 (Unauthorized) ───────┤
│ │
│ ┌──────────────────────────────┐ │
│ │ Токен истек │ │
│ │ auth_status = "Not Auth..." │ │
│ │ Требуется повторный вход │ │
│ └──────────────────────────────┘ │
│ │
└─ Ошибка 500 (Server Error) ───────┤
┌──────────────────────────────┐ │
│ Задержка 1 секунда │ │
│ Повторная попытка │ │
│ Если неудача - лог ошибки │ │
└──────────────────────────────┘ │
┌────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Следующий запуск через 4 минуты │
└─────────────────────────────────────────────────────────┘
```
---
## Установка и настройка
### Требования
- Frappe Framework (версия 14+)
- ERPNext (версия 14+)
- Python 3.10+
- MariaDB 10.6+
- Node.js 16+
### Установка через bench
1. **Переход в директорию bench**
```bash
cd frappe-bench
```
2. **Получение приложения**
```bash
bench get-app https://github.com/your-org/invoice_az.git
```
3. **Установка на сайт**
```bash
bench --site your-site.local install-app invoice_az
```
4. **Миграция базы данных**
```bash
bench --site your-site.local migrate
```
5. **Очистка кэша**
```bash
bench --site your-site.local clear-cache
```
6. **Перезапуск bench**
```bash
bench restart
```
### Первоначальная настройка
#### 1. Настройка ASAN Login
1. Откройте "ASAN Login" в Desk
2. Создайте новую запись
3. Нажмите "Login with ASAN"
4. Отсканируйте QR код мобильным приложением ASAN
5. Подтвердите вход в приложении
6. Выберите сертификат
7. Выберите налогоплательщика
8. Сохраните запись
#### 2. Настройка E-Taxes Settings
1. Откройте "E-Taxes Settings"
2. Заполните основные поля:
- **API Base URL:** `https://new.e-taxes.gov.az`
- **Company:** Ваша компания по умолчанию
- **Default Warehouse:** Склад по умолчанию
3. Настройте параметры:
- **Auto Create Items:** Включить/выключить автосоздание товаров
- **Fuzzy Match Threshold:** 0.95 (95% совпадения)
4. Сохраните настройки
#### 3. Настройка кастомных полей (в приложении jey_erp)
Убедитесь, что следующие кастомные поля существуют:
**Customer:**
- `tax_id` (Data) - ИНН клиента
**Item:**
- `product_group_code` (Data) - Код товарной группы
**Sales Invoice Item:**
- Поля для расчета НДС
### Синхронизация справочников
После установки рекомендуется синхронизировать справочные данные:
1. **Синхронизация товаров**
- Откройте "E-Taxes Item"
- Нажмите "Sync from E-Taxes"
- Дождитесь завершения загрузки
2. **Синхронизация единиц измерения**
- Откройте "E-Taxes Unit"
- Нажмите "Sync from E-Taxes"
3. **Синхронизация контрагентов**
- Откройте "E-Taxes Parties"
- Нажмите "Sync from E-Taxes"
---
## Разработка
### Настройка окружения разработки
1. **Клонирование репозитория**
```bash
cd frappe-bench/apps
git clone https://github.com/your-org/invoice_az.git
```
2. **Установка pre-commit хуков**
```bash
cd invoice_az
pre-commit install
```
3. **Установка зависимостей разработки**
```bash
pip install ruff pytest
npm install -g eslint prettier
```
### Структура кода
**Python модули** (`invoice_az/`)
- `api.py` - API для закупок
- `auth.py` - Аутентификация
- `sales_api.py` - API для продаж
- `send_sales_api.py` - Отправка счетов
- `hooks.py` - Интеграция с Frappe
**JavaScript клиенты** (`invoice_az/client/`)
- `etaxes_common.js` - Общие утилиты
- `purchase_order.js` - Заказы на покупку
- `sales_order.js` - Заказы на продажу
- `sales_invoice.js` - Счета на оплату
- `[entity]_list.js` - Списки справочников
**DocTypes** (`invoice_az/invoice_az/doctype/`)
- Каждый DocType в отдельной папке
- Файлы: `.py`, `.js`, `.json`, `test_*.py`
### Соглашения кодирования
**Python (ruff)**
- Длина строки: 110 символов
- Целевая версия: Python 3.10
- Кавычки: двойные
- Отступы: табы
- Правила: F, E, W, I, UP, B
**JavaScript (eslint)**
- Кавычки: одинарные
- Точка с запятой: обязательна
- Отступы: табы
### Тестирование
**Запуск тестов Python**
```bash
cd frappe-bench
bench --site your-site.local run-tests --app invoice_az
```
**Запуск конкретного теста**
```bash
bench --site your-site.local run-tests --test invoice_az.invoice_az.doctype.asan_login.test_asan_login
```
**Запуск линтера**
```bash
ruff check invoice_az/
ruff format invoice_az/
```
### Логирование и отладка
**Логирование ошибок**
```python
frappe.log_error(
title="E-Taxes API Error",
message=str(error),
reference_doctype="E-Taxes Purchase",
reference_name=purchase_name
)
```
**Информационные логи**
```python
import logging
logger = logging.getLogger(__name__)
logger.info(f"Processing invoice {invoice_id}")
```
**Отладочный вывод**
```python
frappe.msgprint(f"Debug: {variable}")
print(f"Debug: {variable}") # Для консоли bench
```
### Создание нового DocType
1. **Создание через bench**
```bash
bench --site your-site.local new-doctype "E-Taxes New Feature"
```
2. **Редактирование JSON**
```json
{
"name": "E-Taxes New Feature",
"module": "Invoice Az",
"autoname": "field:name",
"fields": [
{
"fieldname": "name",
"fieldtype": "Data",
"label": "Name",
"reqd": 1
}
]
}
```
3. **Создание Python контроллера**
```python
import frappe
from frappe.model.document import Document
class ETaxesNewFeature(Document):
def validate(self):
# Валидация при сохранении
pass
def on_submit(self):
# Действия при отправке
pass
```
4. **Создание JavaScript контроллера**
```javascript
frappe.ui.form.on('E-Taxes New Feature', {
refresh: function(frm) {
// Логика формы
}
});
```
### Создание нового API endpoint
```python
@frappe.whitelist()
def my_new_api_function(param1, param2):
"""
Описание функции
Args:
param1: Описание параметра 1
param2: Описание параметра 2
Returns:
dict: Результат операции
"""
try:
# Валидация прав
frappe.only_for("System Manager")
# Логика обработки
result = process_data(param1, param2)
return {
"success": True,
"data": result
}
except Exception as e:
frappe.log_error(str(e), "API Error")
return {
"success": False,
"error": str(e)
}
```
**Вызов из JavaScript:**
```javascript
frappe.call({
method: 'invoice_az.api.my_new_api_function',
args: {
param1: 'value1',
param2: 'value2'
},
callback: function(r) {
if (r.message.success) {
frappe.msgprint('Success!');
} else {
frappe.msgprint('Error: ' + r.message.error);
}
}
});
```
### Добавление нового клиентского скрипта
1. **Создание файла** `invoice_az/client/my_script.js`
```javascript
frappe.ui.form.on('My DocType', {
refresh: function(frm) {
frm.add_custom_button(__('My Action'), function() {
// Логика кнопки
});
},
field_name: function(frm) {
// Обработчик изменения поля
}
});
```
2. **Регистрация в hooks.py**
```python
doctype_js = {
"My DocType": "client/my_script.js"
}
```
### Git workflow
**Создание новой фичи:**
```bash
git checkout -b feature/my-new-feature
# Разработка
git add .
git commit -m "feat: Add my new feature"
git push origin feature/my-new-feature
# Создание Pull Request
```
**Pre-commit хуки автоматически проверят:**
- Линтинг Python (ruff)
- Линтинг JavaScript (eslint)
- Форматирование (prettier)
- Синтаксис Python (pyupgrade)
### Производительность и оптимизация
**Кэширование:**
```python
# Кэш на 5 минут
cache_key = f"etaxes_invoices_{from_date}_{to_date}"
cached_data = frappe.cache().get_value(cache_key)
if cached_data:
return cached_data
data = fetch_from_api()
frappe.cache().set_value(cache_key, data, expires_in_sec=300)
```
**Массовые операции:**
```python
# Вместо цикла с save()
for item in items:
item.save()
# Используйте bulk_insert
frappe.db.bulk_insert(
"E-Taxes Item",
["name", "etaxes_item_code", "etaxes_item_name"],
[[item.name, item.code, item.name] for item in items]
)
```
**Индексация:**
```python
# В DocType JSON
"fields": [
{
"fieldname": "etaxes_item_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"search_index": 1 # Создает индекс
}
]
```
---
## Документация
### Существующие файлы документации
1. **README.md** (685 байт)
- Краткое описание проекта
- Инструкции по установке
- Инструкции для разработчиков (pre-commit)
2. **claude.md** (34 КБ, 1,400+ строк)
- Подробная документация на русском языке
- Архитектура системы
- Описание API
- Примеры использования
- Технические детали
3. **QA_SEND_INVOICE.md** (15 КБ)
- 27 вопросов и ответов
- Функциональность отправки счетов
- Технические спецификации
- Примеры использования
### Примеры файлов
**invoice.txt** - Пример JSON для создания счета
**invoice asan.txt** - Пример JSON для подписи через ASAN
---
## Вопросы и Ответы (Q&A) - Отправка Sales Invoice в E-Taxes
Дата: 2025-12-15
Задача: Добавление функционала отправки Sales Invoice в E-Taxes
### Технические вопросы и ответы (27 вопросов)
#### 1. Аутентификация
**Q:** Должны ли мы использовать существующую систему аутентификации ASAN Login (которая уже есть в приложении для импорта инвойсов), или нужны отдельные credentials для отправки?
**A:** Использовать существующий ASAN Login
---
#### 2. Доступность кнопки
**Q:** Когда кнопка 'Отправить в E-Taxes' должна быть доступна?
**A:** После Submit (рекомендуется) - кнопка доступна только после того, как Sales Invoice в статусе Submitted
---
#### 3. Процесс подписания
**Q:** Должен ли процесс подписания (sign/withAsanImza) происходить автоматически после создания инвойса, или это должна быть отдельная кнопка?
**A:** Да, автоматически после создания. Кастомные поля создаются в приложении jey_erp (файл custom_fields.py).
---
#### 4. Данные клиента (TIN и Object Name)
**Q:** Где хранится информация о клиенте для отправки в E-Taxes (TIN, objectName)?
**A:** Поле TIN - это tax_id. Поле object_name пока создается как тестовое текстовое поле, потом будет заменено.
---
#### 5. Product Group Code для товаров
**Q:** Как определять productGroupCode для товаров (items)?
**A:** Есть поле в Item, которое называется product_group_code, текстового типа.
---
#### 6. Сохранение данных после отправки
**Q:** Какие данные нужно сохранить после успешной отправки?
**A:**
- Invoice ID
- Serial Number
- Verification Code
- Статус отправки
---
#### 7. Обработка ошибок API
**Q:** Как обрабатывать ошибки API?
**A:** Показать ошибку и остановить процесс.
---
#### 8. Тип НДС для товаров
**Q:** Как определять тип НДС для товаров (vat18, vat0, vatFree, exempt)?
**A:** В файле custom_fields.py уже созданы поля для Sales Invoice Item:
- `vat_18_percent_with_amount` - VAT 18% with amount
- `vat_amount` - VAT Amount (calculated)
- `vat_0_percent_with_amount` - VAT 0% with amount
- `amount_without_vat` - Amount without VAT
- `vat_free_amount` - VAT free amount
- `tax_article` - Tax Article (Link to Tax Article doctype)
Пример кода из custom_fields.py:
```python
"Sales Invoice Item": [
dict(
fieldname='vat_18_percent_with_amount',
label='VAT 18% with amount',
fieldtype='Currency',
insert_after='amount',
in_list_view=1,
read_only=1,
columns=2,
precision=2
),
dict(
fieldname='vat_amount',
label='VAT Amount',
fieldtype='Currency',
insert_after='vat_18_percent_with_amount',
in_list_view=1,
columns=2,
precision=2,
read_only=1,
description='Calculated as: VAT 18% with amount - Amount'
),
# ... другие поля
]
```
---
#### 9. Unit Code для единиц измерения
**Q:** Откуда брать unitCode для единиц измерения?
**A:** Поле unit_code необязательно. Можно просто использовать текстовое имя единицы измерения из табличной части Sales Invoice Item. E-Taxes принимает любой текст (даже "test123"), но для стандартных единиц можно использовать следующие коды:
```json
{
"items": [
{"code": "666", "name": {"az": "ədəd", "ru": "штук", "en": "piece"}},
{"code": "391836", "name": {"az": "km", "ru": "км", "en": "km"}},
{"code": "529937", "name": {"az": "kq", "ru": "кг", "en": "kg"}},
{"code": "581879", "name": {"az": "l", "ru": "л", "en": "l"}},
{"code": "207201", "name": {"az": "m", "ru": "м", "en": "m"}},
{"code": "6666", "name": {"az": "m2", "ru": "м2", "en": "m2"}},
{"code": "839111", "name": {"az": "m3", "ru": "м3", "en": "m3"}},
{"code": "525166", "name": {"az": "q", "ru": "г", "en": "g"}},
{"code": "116094", "name": {"az": "sm", "ru": "см", "en": "cm"}},
{"code": "864211", "name": {"az": "sm3", "ru": "см3", "en": "cm3"}},
{"code": "544580", "name": {"az": "t", "ru": "т", "en": "t"}}
]
}
```
---
#### 10. Дополнительные поля (senderObjectCode, vehicleRegistrationNumber)
**Q:** Что делать с полями senderObjectCode и vehicleRegistrationNumber?
**A:** Оставить пустыми
---
#### 11. Предотвращение повторной отправки
**Q:** Должны ли мы предотвращать повторную отправку?
**A:** Да, блокировать повтор
---
#### 12. Текст кнопки
**Q:** Какой текст должен быть на кнопке отправки?
**A:** "Send to E-Taxes"
---
#### 13. Комментарии в инвойсе
**Q:** Что делать с полями invoiceComment и invoiceComment2?
**A:** Есть поля comment и additional_comment в Sales Invoice.
---
#### 14. Диалог подтверждения
**Q:** Показывать ли диалог подтверждения перед отправкой?
**A:** Да, два диалога подтверждения:
1. "Send this invoice to E-Taxes?"
2. "Are you sure?"
---
#### 15. Кнопка в списке инвойсов
**Q:** Нужна ли кнопка в list view (списке инвойсов)?
**A:** Пока что без этого. Возможно будет добавлено позже.
---
#### 16. Генерация ID для товаров
**Q:** Как генерировать ID для товаров (item id в формате '44f5986a-2d3c-45d6-bb14-79a899******')?
**A:** Случайный UUID
---
#### 17. Фиксированные поля JSON
**Q:** Какие значения использовать для полей type, kind, sourceSystem?
**A:** Использовать значения как в примере:
- `type` = "current"
- `kind` = "defaultInvoice"
- `sourceSystem` = "lk"
---
#### 18. Валидация данных
**Q:** Нужно ли валидировать данные перед отправкой?
**A:** Да, необходимо проверить:
- TIN клиента (tax_id)
- Product codes у товаров
- Unit codes (опционально)
---
#### 19. Маппинг единиц измерения (UOM)
**Q:** Как маппить единицы измерения из ERPNext UOM в коды E-Taxes? Например, если в Sales Invoice Item указано 'Nos' или 'Unit' - какой код использовать?
**A:** Просто брать имя единицы измерения из табличной части. Поле unit_code необязательно, E-Taxes принимает текстовое название.
---
#### 20. Pricing поля для товаров
**Q:** Что использовать для pricePerUnit и cost в items? В примере оба = 1, но у нас есть rate, amount и т.д.
**A:**
- `rate``pricePerUnit`
- `amount``cost`
---
#### 21. Кнопка после отправки
**Q:** Что делать с кнопкой после успешной отправки (ты говорил блокировать повтор)?
**A:** Заменить кнопку на индикатор статуса с информацией об отправке.
---
#### 22. Язык сообщений
**Q:** На каком языке показывать сообщения об ошибках и подтверждения?
**A:** English
---
#### 23. Receiver Name (юридическое название)
**Q:** Про receiver.name - нужно искать клиента в E-Taxes Customer Mappings, найти соответствующий E-Taxes Customers и оттуда брать name?
**A:** Да, из E-Taxes Customers. Искать соответствие в e-taxes-settings (в том, который помечен как default), там будет название из E-Taxes.
---
#### 24. Частичная ошибка (создание успешно, подписание failed)
**Q:** Если создание инвойса (invoice create) прошло успешно, но подписание (signing) провалилось - что делать?
**A:** Сохранить invoice ID и serial number, но показать ошибку подписания. Установить статус = "Created, not signed".
---
#### 25. Progress dialog
**Q:** Нужно ли показывать progress dialog во время отправки (как в существующей функции импорта)?
**A:** Простой loading индикатор (frappe.show_alert с loading).
---
#### 26. Тексты подтверждения
**Q:** Тексты для двух диалогов подтверждения - какие должны быть?
**A:**
1. Первый диалог: "Send this invoice to E-Taxes?"
2. Второй диалог: "Are you sure?"
---
#### 27. Tracking doctype
**Q:** Нужно ли создавать tracking doctype (типа E-Taxes Sales) для отправленных инвойсов?
**A:** Да, создать **E-Taxes Sales Outbox** для отслеживания отправленных инвойсов.
---
### Техническая информация по API Endpoints
#### 1. Generate Serial Number
```
POST https://new.e-taxes.gov.az/api/po/invoice/public/v1/generateSerialNumber/defaultInvoice
```
**Response:**
```json
{
"serialNumber": "MT25121128****"
}
```
#### 2. Create Invoice
```
POST https://new.e-taxes.gov.az/api/po/invoice/public/v2/invoice
```
**Response:**
```json
{
"id": "b7bd1f20-4bdb-4e7b-bf18-c1f9bd******"
}
```
#### 3. Sign Invoice
```
POST https://new.e-taxes.gov.az/api/po/invoice/public/v1/invoice/sign/withAsanImza
```
**Request:**
```json
{
"type": "send",
"invoiceIds": ["invoice-id-here"]
}
```
**Response:**
```json
{
"status": "ok",
"error": null,
"verificationCode": "4147"
}
```
---
### Примеры файлов с данными
**Пример создания инвойса:**
`/home/frappe/frappe-bench/apps/invoice_az/invoice.txt`
**Пример подписания:**
`/home/frappe/frappe-bench/apps/invoice_az/invoice asan.txt`
**Кастомные поля:**
`/home/frappe/frappe-bench/apps/jey_erp/jey_erp/custom_fields.py`
---
### Существующие поля
**Sales Invoice Item:**
- vat_18_percent_with_amount
- vat_amount
- vat_0_percent_with_amount
- amount_without_vat
- vat_free_amount
- tax_article
**Sales Invoice:**
- comment
- additional_comment
**Customer:**
- tax_id (TIN)
**Item:**
- product_group_code
---
### Workflow процесса отправки Sales Invoice
```
┌─────────────────────────────────────────────────────────┐
│ 1. Пользователь нажимает "Send to E-Taxes" │
│ на submitted Sales Invoice │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. Первый диалог подтверждения: │
│ "Send this invoice to E-Taxes?" │
└─────────────────────┬───────────────────────────────────┘
│ Да
┌─────────────────────────────────────────────────────────┐
│ 3. Второй диалог подтверждения: │
│ "Are you sure?" │
└─────────────────────┬───────────────────────────────────┘
│ Да
┌─────────────────────────────────────────────────────────┐
│ 4. Показать loading индикатор │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 5. Валидация данных: │
│ ✓ Проверка tax_id клиента (TIN) │
│ ✓ Проверка product_group_code у всех товаров │
│ ✓ Проверка что инвойс еще не отправлен │
│ (блокировка повтора через Outbox) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 6. Получение serial number от API │
│ POST /generateSerialNumber/defaultInvoice │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 7. Формирование JSON payload: │
│ │
│ receiver: │
│ - name: из E-Taxes Customers (через mapping) │
│ - tin: из tax_id │
│ - objectName: из custom field │
│ │
│ items: (для каждого item) │
│ - id: случайный UUID │
│ - productName: item_name │
│ - productGroupCode: product_group_code │
│ - quantity: qty │
│ - pricePerUnit: rate │
│ - cost: amount │
│ - unit: UOM name (текст) │
│ │
│ VAT mapping: │
│ - vat_18_percent_with_amount → vat18 │
│ - vat_0_percent_with_amount → vat0 │
│ - amount_without_vat → exempt │
│ - vat_free_amount → vatFree │
│ │
│ comments: │
│ - invoiceComment: comment │
│ - invoiceComment2: additional_comment │
│ │
│ Фиксированные значения: │
│ - type: "current" │
│ - kind: "defaultInvoice" │
│ - sourceSystem: "lk" │
│ - senderObjectCode: "" (пусто) │
│ - vehicleRegistrationNumber: "" (пусто) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 8. Отправка на создание инвойса │
│ POST /api/po/invoice/public/v2/invoice │
│ Получение invoice_id │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 9. Автоматическое подписание инвойса │
│ POST /invoice/sign/withAsanImza │
│ Body: {"type": "send", "invoiceIds": [...]} │
│ Получение verification_code │
└─────────────────────┬───────────────────────────────────┘
├─ Успех ────────────────────────────┐
│ │
│ Статус: "Sent and Signed" │
│ │
└─ Ошибка подписания ───────────────┤
Статус: "Created, not signed" │
Сохранить ID и serial number │
Показать ошибку │
┌────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 10. Создание E-Taxes Sales Outbox record │
│ - sales_invoice: link to Sales Invoice │
│ - etaxes_invoice_id: invoice ID │
│ - serial_number: serial number │
│ - verification_code: verification code (if signed) │
│ - send_date: now() │
│ - send_status: статус отправки │
│ - error_message: сообщение об ошибке (if any) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 11. Сохранение результатов в Sales Invoice │
│ (опционально - через custom fields или связь) │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 12. Заменить кнопку на индикатор статуса │
│ Показать: │
│ - Invoice ID │
│ - Serial Number │
│ - Verification Code │
│ - Статус отправки │
└─────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 13. Показать сообщение об успехе пользователю │
│ "Invoice successfully sent to E-Taxes" │
│ или сообщение об ошибке │
└─────────────────────────────────────────────────────────┘
```
---
### Важные замечания по реализации
1. **Двойное подтверждение** - обязательно два диалога для предотвращения случайной отправки
2. **Блокировка повтора** - проверять существование записи в E-Taxes Sales Outbox
3. **Частичные ошибки** - если создание прошло, но подписание нет - сохранить данные и показать ошибку
4. **Маппинг клиента** - брать название из E-Taxes Customers через маппинг в E-Taxes Settings
5. **Единицы измерения** - можно использовать текстовое название, unit_code необязателен
6. **VAT поля** - использовать существующие кастомные поля из Sales Invoice Item
7. **UUID для items** - генерировать случайный UUID для каждого товара
8. **Сообщения на английском** - все пользовательские сообщения на английском языке
9. **Loading индикатор** - простой frappe.show_alert, не сложный progress dialog
10. **Tracking** - создать E-Taxes Sales Outbox для истории отправок
---
## Контакты и поддержка
**Автор:** Jey ERP
**Email:** info@jeyerp.az
**Website:** https://jeyerp.az
**E-Taxes (Правительство Азербайджана):**
**Website:** https://new.e-taxes.gov.az
**ASAN Login:** https://asan.gov.az
---
## Лицензия
Проект распространяется под лицензией **Unlicense** (общественное достояние).
```
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any means.
```
Полный текст лицензии см. в файле `license.txt`.
---
## Заключение
Проект **Invoice Az** представляет собой комплексное решение для интеграции Frappe/ERPNext с государственной системой электронного налогообложения Азербайджана E-Taxes.
### Ключевые преимущества:
1. **Полная автоматизация** - импорт входящих и экспорт исходящих счетов
2. **Интеллектуальное сопоставление** - автоматическое сопоставление данных с точностью 95%
3. **Безопасность** - интеграция с государственной системой ASAN Login
4. **Масштабируемость** - обработка до 200 счетов за запрос
5. **Надежность** - автоматическое обновление токенов, обработка ошибок, логирование
6. **Удобство** - интуитивный интерфейс, массовые операции, подробные подсказки
### Технические характеристики:
- **17 кастомных DocTypes** для хранения данных
- **7,198 строк Python кода** для бизнес-логики
- **9,786 строк JavaScript кода** для клиентской части
- **3 файла документации** (50 КБ) для пользователей и разработчиков
- **Полная интеграция** с стандартными DocTypes ERPNext
Проект активно развивается и поддерживается командой Jey ERP.
---
*Версия документа: 1.0*
*Дата создания: 2025-12-16*
*Автор документа: Claude Code*