Перейти до змісту

Плагін MedocExchange — Документація

Загальний опис

MedocExchange — платний плагін для модуля Essentials системи ESWF.

Два рівні функціональності:

Рівень Що входить Статус
Production (MVP) Експорт Invoiceвидаткова накладна (J1203001 / F1203001) → ZIP → імпорт бухгалтером у M.E.Doc ✅ стабільний
🧪 Experimental Податкові накладні в DOP: модель TaxInvoice, генерація J1201006 XML, імпорт вхідних ПН, звірка з ЄРПН ⚠️ окремий future-track

🧪 Experimental: податкові накладні в DOP

⚠️ Важливо — читайте до використання

Робота з податковими накладними (J1201006) та ЄРПН у цьому плагіні — це experimental-track, винесений як окрема майбутня ініціатива.

Початковий scope інтеграції (й той, який рекомендовано для продакшена): 1. DOP генерує лише видаткові накладні формату J1203001 з Invoice. 2. Бухгалтер завантажує XML у M.E.Doc. 3. Податкові накладні створюються та реєструються у M.E.Doc, а не в DOP.

Чому tax invoice в DOP — ризиковано: - Податкове законодавство України змінюється дуже швидко (формати ПН, правила блокування, КВЕДи, нові теги у XML). - M.E.Doc оновлює формати оперативно; DOP не може гарантувати той самий темп без спеціалізованої команди податкового супроводу. - Розходження з ЄРПН → штрафи клієнту → репутаційний ризик платформи.

Поточний статус experimental-блоку (розділ «Flow 2026-04-22» нижче): - Код написаний та покритий тестами (backend). - UI/frontend інтеграції немає. - Може бути видалений або суттєво перероблений у наступних релізах. - Не використовувати у production без додаткового юридичного/податкового аудиту.

Коли повернемося: якщо виникне клієнтський попит на повний tax-accounting всередині DOP і з'явиться виділена команда податкового супроводу — тоді ця секція стане частиною окремого модуля tax-accounting з власним lifecycle. Поки — «колись, може, зробимо».

Production-flow (рекомендований) описано нижче — розділ «Структура XML (J1203001)», «Сервіс генерації XML», «Management Command load_uktzed», «Валідація перед експортом».


Призначення plugin'а в цілому: обмін документами з програмою M.E.Doc / СОТА та ДПС України — експорт видаткових накладних (MVP), + experimental flow для податкових накладних та звірки з ЄРПН.

Параметр Значення
App label medoc_exchange
URL prefix /api/v1/medoc/
Plugin key medoc_exchange
Тип ліцензії Платна (перевірка через shop.License)
Формати XML J1203001 / F1203001 (видаткова накладна), J1201006 / F1201006 (податкова накладна)
Кодування XML windows-1251
Архів ZIP (для експорту видаткових)

🧪 Flow 2026-04-22 — experimental: повний цикл роботи з податковими накладними

⚠️ Experimental. Див. попередження на початку документа. Не використовувати без додаткового аудиту.

Квітень 2026 — в коді модуля з'явився окремий experimental-блок, який охоплює не лише експорт Invoice у формат видаткової накладної, а й lifecycle податкової накладної (ПН), яка реєструється в ЄРПН. Цей блок розглядається як відкладена окрема ініціатива (див. попередження вище) і не входить у production-scope MVP-інтеграції.

Три головних процеси

┌────────────────────────┐       ┌────────────────────────┐       ┌────────────────────────┐
│  1. EXPORT OUT         │       │  2. IMPORT IN          │       │  3. RECONCILE          │
│                        │       │                        │       │                        │
│  Invoice / Purch.Inv.  │       │  XML від постачальника │       │  CSV-витяг з ЄРПН      │
│        ↓               │       │        ↓               │       │        ↓               │
│  build_tax_invoice_    │       │  parse_medoc_xml()     │       │  parse_erpn_extract()  │
│  from_source()         │       │        ↓               │       │        ↓               │
│        ↓               │       │  import_as_tax_invoice │       │  reconcile()           │
│  TaxInvoice (draft)    │       │        ↓               │       │        ↓               │
│        ↓               │       │  TaxInvoice (direction │       │  matched /             │
│  TaxInvoiceExporter    │       │   ='in')               │       │  amount_mismatch /     │
│        ↓               │       │        ↓               │       │  missing_in_erpn /     │
│  J1201006 XML          │       │  try_match_existing_   │       │  extra_in_erpn         │
│        ↓               │       │  purchase_invoice()    │       │                        │
│  → M.E.Doc / СОТА      │       │                        │       │                        │
│  → ЄРПН                │       │                        │       │                        │
└────────────────────────┘       └────────────────────────┘       └────────────────────────┘

Модель TaxInvoice

Файл: backend/medoc_exchange/models.py

TaxInvoice — окрема сутність (не просто згенерований XML), що має власний lifecycle ЄРПН: draft → submitted → registered → blocked/rejected.

Поле Опис
direction out (видана — наше ПДВ-зобов'язання) / in (отримана — наш податковий кредит)
tax_type standard / adjustment (розрахунок коригування) / summary (зведена)
number, date Реквізити ПН
source_invoice FK Джерело — Invoice (sale)
source_purchase_invoice FK Джерело — PurchaseInvoice (purchase)
source_payment FK Джерело — IncomingPayment (аванс)
seller_tin/name, buyer_tin/name Денормалізовані реквізити (зберігаються, навіть якщо контрагента видалили)
total_net, total_vat, total_gross Суми
erpn_status draft / submitted / registered / blocked / rejected
erpn_sent_at, erpn_reg_date, erpn_error Історія реєстрації в ЄРПН
xml_content, xml_filename Збережений XML (для аудиту)
reconciled_at, reconciliation Результат останньої звірки з ЄРПН

1. Експорт податкової накладної

Helper: build_tax_invoice_from_source(source, tenant, direction, ...)

Створює TaxInvoice з будь-якого джерела (Invoice / PurchaseInvoice / IncomingPayment), автоматично: - підставляє ІПН / назву продавця та покупця; - міняє місцями ІПН-и при direction='in' (вхідна ПН — продавець стає постачальник, покупець — ми); - переносить суми net / vat / gross.

Клас: TaxInvoiceExporter(tax_invoice) — будує XML J1201006/F1201006.

exporter = TaxInvoiceExporter(tax_invoice)
xml_bytes = exporter.build_xml()       # windows-1251 encoded
filename  = exporter.get_filename()    # '010001234567890J12010060000010001540.xml'

Fallback-ланцюжок вибору рядків: 1. source Invoice.lines (QuerySet) → якщо не порожній. 2. source PurchaseInvoice.lines → якщо не порожній. 3. source IncomingPayment → одна aggregate-рядок «Аванс за договором Х». 4. Aggregate summary з ti.total_net / ti.total_vat (коли рядків взагалі немає).

Константи формату J1201006:

Константа Значення
DOC_CODE_LEGAL 'J12'
DOC_CODE_FOP 'F12'
DOC_SUB '010' (податкова накладна)
DOC_VER '06' (поточна версія схеми)

⚠️ Порівняння з видатковою накладною: J1203001 (DOC_SUB='030') — це видаткова накладна (первинний документ продажу товарів/послуг). J1201006 (DOC_SUB='010') — податкова накладна, що реєструється в ЄРПН. Це різні документи з різними XML-тегами.

2. Імпорт вхідних податкових накладних

Сервіс: backend/medoc_exchange/services/importer.py

from medoc_exchange.services.importer import parse_medoc_xml, import_as_tax_invoice

# Розбір XML
parsed = parse_medoc_xml(xml_bytes)  # автоматичне декодування windows-1251
# {'doc_type': 'J1201006', 'seller_tin': '12345678', 'buyer_tin': '98765432',
#  'date': date(2026,4,15), 'number': '154', 'total_gross': Decimal('1200.00'),
#  'lines': [...]}

# Створення TaxInvoice
tax_invoice = import_as_tax_invoice(parsed, tenant=request.tenant)  # direction='in'

# Спроба прив'язки до існуючого PurchaseInvoice
pi = try_match_existing_purchase_invoice(parsed, tenant=request.tenant)
# Matcher: (supplier_invoice_number, date, seller_tin) → PurchaseInvoice

API: POST /api/v1/medoc/import/ (multipart file=<xml>).

3. Звірка з ЄРПН

Сервіс: backend/medoc_exchange/services/reconciliation.py

Раз на місяць бухгалтер вивантажує з кабінету ДПС CSV-витяг своїх накладних з ЄРПН. Сервіс розкладає їх у 4 кошики:

Bucket Опис Що робити
matched Номер, дата, ІПН, сума збіглися ✅ ок
amount_mismatch Номер/дата/ІПН збіглися, сума різна ⚠️ розібратись — можлива помилка обліку або не зареєстроване коригування
missing_in_erpn У нас є — в ЄРПН нема 🔴 критично — ПН не зареєстровано, штраф
extra_in_erpn В ЄРПН є — у нас нема 🟡 вхідні від постачальника, які ми не зафіксували — імпортувати

Толерантний CSV-парсер parse_erpn_extract(csv_text) підтримує: - Декілька форматів дати: YYYY-MM-DD, DD.MM.YYYY, DDMMYYYY. - Числа: 1234.56, "1 234,56" (з пробілом-роздільником тисяч та комою-десятковим).

API: POST /api/v1/medoc/reconcile/ (multipart file=<csv>).

{
  "summary": {
    "matched_count": 24,
    "amount_mismatch_count": 1,
    "missing_in_erpn_count": 2,
    "extra_in_erpn_count": 3
  },
  "matched": [...],
  "amount_mismatch": [{"our": {...}, "erpn": {...}, "diff": "50.00"}],
  "missing_in_erpn": [...],
  "extra_in_erpn": [...]
}

API ViewSet для TaxInvoice

Маршрут: /api/v1/medoc/tax-invoices/ (через TaxInvoiceViewSet).

Action Метод Опис
list / retrieve / create / update / destroy стандартний ModelViewSet CRUD
from_invoice POST /tax-invoices/from-invoice/{invoice_id}/ Створити ПН з Invoice
from_purchase_invoice POST /tax-invoices/from-purchase-invoice/{pi_id}/ Створити вхідну ПН з PurchaseInvoice
xml GET /tax-invoices/{id}/xml/ Згенерувати J1201006 XML (windows-1251)
mark_registered POST /tax-invoices/{id}/mark-registered/ Відмітити як зареєстровану в ЄРПН

Тести

Файл: backend/medoc_exchange/tests/test_tax_invoice.py — 6 тестів:

Тест Що перевіряє
TestTaxInvoiceBuilder.test_from_sales_invoice_is_outgoing Правильне заповнення TIN-ів для direction='out'
TestTaxInvoiceBuilder.test_swaps_tins_when_incoming Swap ІПН для direction='in'
TestTaxInvoiceExporterXML.test_xml_has_declarhead_and_body XML містить DECLARHEAD/DECLARBODY + правильні теги
TestImporterRoundTrip.test_parse_back_exported_xml Round-trip export → import
TestReconciliation.test_reconcile_buckets Розкладання по 4 кошиках
TestParseExtractCSV.test_parser_tolerates_variants Толерантність CSV-парсера до форматів


Структура файлів

backend/medoc_exchange/
├── __init__.py
├── apps.py                                  # MedocExchangeConfig
├── admin.py                                 # UKTZEDDirectory в Django Admin
├── models.py                                # UKTZEDDirectory
├── serializers.py                           # UKTZEDDirectorySerializer
├── views.py                                 # 4 endpoint + ViewSet
├── urls.py                                  # маршрутизація
├── migrations/
│   ├── __init__.py
│   └── 0001_initial.py                      # створення таблиці uktzeddirectory
├── services/
│   ├── __init__.py
│   ├── validator.py                         # validate_invoice_for_export()
│   └── exporter.py                          # MedocExporter — генерація XML/ZIP
└── management/
    └── commands/
        └── load_uktzed.py                   # python manage.py load_uktzed

Пов'язані зміни в існуючих файлах:

backend/essentials/models/item.py            # + поле uktzed_code (ForeignKey)
backend/essentials/migrations/0007_item_uktzed_code.py
backend/eswf/settings/base.py                # + 'medoc_exchange' в INSTALLED_APPS
backend/eswf/urls.py                         # + path('api/v1/medoc/', ...)

Моделі

UKTZEDDirectory (medoc_exchange/models.py)

Глобальний довідник УКТЗЕД. Не прив'язаний до тенанта — загальний для всіх.

Поле Тип Опис
code CharField(10) Код УКТЗЕД (до 10 цифр), унікальний, індексований
description CharField(1000) Опис англійською
description_ua CharField(1000) Опис українською
is_service BooleanField True для послуг (префікс 00)
class UKTZEDDirectory(models.Model):
    code           = models.CharField(max_length=10, unique=True, db_index=True)
    description    = models.CharField(max_length=1000)
    description_ua = models.CharField(max_length=1000, blank=True)
    is_service     = models.BooleanField(default=False)

    class Meta:
        ordering = ['code']

Зміна в Item (essentials/models/item.py)

Додано поле uktzed_code — обов'язкове для M.E.Doc експорту:

uktzed_code = models.ForeignKey(
    'medoc_exchange.UKTZEDDirectory',
    on_delete=models.SET_NULL, null=True, blank=True,
    verbose_name='UKTZED Code',
    help_text='УКТ ЗЕД — код для експорту в M.E.Doc.',
)

API Endpoints

Всі захищені JWT. Три з чотирьох додатково перевіряють активну ліцензію плагіну.

GET /api/v1/medoc/uktzed/

Пошук по довіднику УКТЗЕД (без ліцензії).

Параметр Опис
?search= Пошук по code, description, description_ua
?ordering=code Сортування

Відповідь:

{
  "count": 15000,
  "results": [
    {"id": 1, "code": "0101100000", "description_ua": "Коні чистокровні племінні", "is_service": false},
    ...
  ]
}


POST /api/v1/medoc/validate/

Перевірка накладної перед експортом. Повертає список помилок або {"valid": true}.

Тіло запиту:

{ "invoice_id": 42 }

Відповідь (успіх):

{ "valid": true }

Відповідь (помилки) — HTTP 422:

{
  "valid": false,
  "errors": [
    {"field": "organization.edrpou", "message": "Organization \"ТОВ Приклад\": EDRPOU or INN must be set."},
    {"field": "lines[2].item.uktzed_code", "message": "Line 2: item \"Товар Б\" has no UKTZED code."}
  ]
}


GET /api/v1/medoc/export/<invoice_id>/

Генерація та завантаження ZIP-архіву з XML-файлом формату M.E.Doc.

  • Відповідь: application/zip, Content-Disposition: attachment; filename="...zip"
  • При невалідних даних — HTTP 422 з JSON помилками (аналогічно validate)

GET /api/v1/medoc/cloud/<invoice_id>/

Повернення даних накладної у структурованому JSON для хмарного агента або інтеграції з СОТА.

Відповідь:

{
  "doc_type": "J1203001",
  "xml_filename": "010001234567890J12030001000001540 0.xml",
  "date": "23022026",
  "number": "154",
  "seller": {
    "name": "ТОВ Продавець",
    "full_name": "Товариство з обмеженою відповідальністю \"Продавець\"",
    "tin": "12345678",
    "is_vat_payer": true,
    "tax_system": "general"
  },
  "buyer": {
    "name": "ТОВ Покупець",
    "tin": "98765432",
    "client_type": "company"
  },
  "total_net": "1000.00",
  "total_vat": "200.00",
  "total_gross": "1200.00",
  "currency": "UAH",
  "lines": [
    {
      "name": "Товар А",
      "uktzed_code": "8471300000",
      "uktzed_description": "Машини обчислювальні портативні...",
      "unit": "шт",
      "quantity": "2.000",
      "price": "500.0000",
      "amount": "1000.0000",
      "vat_rate": "20",
      "vat_amount": "200.0000"
    }
  ]
}


Структура XML (J1203001 / F1203001)

Формат відповідає вимогам ДПС / M.E.Doc для видаткових накладних.

<?xml version="1.0" encoding="windows-1251"?>
<DECLAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <DECLARHEAD>
    <C_DOC>J12</C_DOC>         <!-- J12 = юр.особа | F12 = ФОП -->
    <C_DOC_SUB>030</C_DOC_SUB>
    <C_DOC_VER>01</C_DOC_VER>
    <C_DOC_TYPE>0</C_DOC_TYPE>  <!-- 0 = оригінал, 1 = уточнюючий -->
    <C_DOC_CNT>1</C_DOC_CNT>
    <D_FILL>23022026</D_FILL>   <!-- DDMMYYYY -->
    <HKSTI>12345678</HKSTI>     <!-- ЄДРПОУ або ІПН продавця -->
  </DECLARHEAD>

  <DECLARBODY>
    <HFILL>23022026</HFILL>             <!-- дата документа DDMMYYYY -->
    <HNUM>154</HNUM>                    <!-- номер документа -->
    <HTIN_SEL>12345678</HTIN_SEL>       <!-- ЄДРПОУ/ІПН продавця -->
    <HNAME_SEL>ТОВ Продавець</HNAME_SEL>
    <HTIN_BUY>98765432</HTIN_BUY>       <!-- ЄДРПОУ/ІПН покупця -->
    <HNAME_BUY>ТОВ Покупець</HNAME_BUY>

    <!-- Рядки таблиці: R01, R02, ... (нумерація з 01) -->
    <R01G1S>Товар А</R01G1S>            <!-- назва номенклатури -->
    <R01G7>8471300000</R01G7>           <!-- код УКТЗЕД -->
    <R01G32>шт</R01G32>                 <!-- одиниця виміру -->
    <R01G6>2</R01G6>                    <!-- кількість -->
    <R01G5>500.00</R01G5>               <!-- ціна без ПДВ -->
    <R01G11>1000.00</R01G11>            <!-- сума без ПДВ -->
    <R01G14>200.00</R01G14>             <!-- сума ПДВ -->

    <HSUM>1000.00</HSUM>                <!-- загальна сума без ПДВ -->
    <HPDV>200.00</HPDV>                 <!-- загальна сума ПДВ -->
    <HZAG>1200.00</HZAG>                <!-- загальна сума з ПДВ -->
  </DECLARBODY>

</DECLAR>

Визначення типу документа

Organization.tax_system C_DOC Формат файлу
general (ТОВ/АТ) J12 J1203001
simplified (ФОП) F12 F1203001

Формат назви файлу

M.E.Doc вимагає суворий формат імені XML-файлу:

РЕГІОН(2) + РАЙОН(3) + ЄДРПОУ(8-10) + КОД_ДОКУМ(8) + ПОРЦІЯ(6) + НОМ_ДОКУМ(6) + ТИП(1) + .xml
Частина Довжина Приклад Опис
РЕГІОН 2 01 Код регіону (Вінниця=01, Київ=80 тощо)
РАЙОН 3 000 Код підрозділу ДПС
ЄДРПОУ 8-10 0012345678 ЄДРПОУ організації (доповнюється нулями до 10)
КОД_ДОКУМ 8 J1203001 J12+SUB+VER або F12+SUB+VER
ПОРЦІЯ 6 000001 Порція файлів (зазвичай 000001)
НОМ_ДОКУМ 6 000154 Номер документа (доповнюється нулями)
ТИП 1 0 0=оригінал, 1=уточнюючий

Приклад: 01000012345678J12030010000010001540.xml

Поля region_code та district_code наразі не є полями моделі Organization. Поточна реалізація використовує значення за замовчуванням 01 і 000. Для точного формування імені — додайте ці поля до Organization та міграцію.


Логіка перевірки ліцензії

POST /api/v1/medoc/validate/
GET  /api/v1/medoc/export/<id>/
GET  /api/v1/medoc/cloud/<id>/
shop.License.objects.filter(
    tenant=request.tenant,
    product__plugin_key='medoc_exchange',
    is_active=True,
).exists()
    False ──► HTTP 403: "Active MedocExchange license required."
    True  ──► продовження виконання

Активація ліцензії відбувається через App Store ESWF (/api/v1/shop/): 1. Користувач купує продукт з plugin_key='medoc_exchange' 2. Система створює запис License(tenant=..., product=..., is_active=True) 3. Плагін починає працювати для даного тенанту


Валідація перед експортом

Файл: services/validator.py

Функція validate_invoice_for_export(invoice) виконує такі перевірки:

# Перевірка Поле помилки
1 invoice.number не порожній number
2 invoice.date не порожня date
3 invoice.organization вказана organization
4 organization.edrpou або organization.inn заповнені organization.edrpou
5 client.edrpou або client.inn заповнені client.edrpou
6 Накладна має хоча б один рядок lines
7 Кожен товар у рядку має uktzed_code lines[N].item.uktzed_code

Повертає [] (порожній список) якщо все в порядку.


Сервіс генерації XML

Файл: services/exporter.py

Клас MedocExporter

exporter = MedocExporter(invoice)

# Отримати XML як bytes (windows-1251)
xml_bytes = exporter.build_xml()

# Отримати ім'я файлу
filename = exporter.get_filename()  # "01000...J12030001000015400.xml"

# Отримати ZIP (bytes, filename)
zip_bytes, zip_name = exporter.build_zip()

Константи

Константа Значення Опис
DOC_CODE_LEGAL 'J12' Юридична особа
DOC_CODE_FOP 'F12' ФОП
DOC_SUB '030' Підтип документа
DOC_VER '01' Версія форми

Management Command: load_uktzed

Файл: management/commands/load_uktzed.py

Формати вхідного файлу

JSON:

[
  {
    "code": "0101100000",
    "description": "Pure-bred breeding horses",
    "description_ua": "Коні чистокровні племінні",
    "is_service": false
  }
]

CSV:

code,description,description_ua,is_service
0101100000,Pure-bred breeding horses,Коні чистокровні племінні,false
0201100000,Carcasses and half-carcasses,Туші та напівтуші,false

Запуск

# Завантаження з JSON
python manage.py load_uktzed uktzed.json

# Завантаження з CSV з очищенням існуючих записів
python manage.py load_uktzed uktzed.csv --clear

Де взяти базу УКТЗЕД

  1. Завантажити Excel з сайту Держмитслужби: https://customs.gov.ua (розділ «Класифікатор УКТ ЗЕД»)
  2. Конвертувати Excel → JSON одноразовим скриптом:
import pandas as pd, json

df = pd.read_excel('uktzed.xlsx', dtype=str)
df.columns = ['code', 'description_ua']
df['description'] = df['description_ua']
df['is_service'] = False

with open('uktzed.json', 'w', encoding='utf-8') as f:
    json.dump(df.to_dict(orient='records'), f, ensure_ascii=False, indent=2)
  1. Запустити команду:
    python manage.py load_uktzed uktzed.json
    

Розгортання та міграції

cd backend

# Застосувати міграції (створює таблицю uktzeddirectory + поле item.uktzed_code)
python manage.py migrate

# Завантажити довідник УКТЗЕД
python manage.py load_uktzed uktzed.json

# Перевірити API (потребує активної ліцензії)
curl -H "Authorization: Bearer <token>" \
     http://localhost:8000/api/v1/medoc/uktzed/?search=8471

Налаштування в Django

settings/base.py

INSTALLED_APPS = [
    ...
    'medoc_exchange',   # ← додано
]

eswf/urls.py

path('api/v1/medoc/', include('medoc_exchange.urls')),

Схема взаємодії компонентів

Frontend DOP
    │  1. POST /api/v1/medoc/validate/  {"invoice_id": 42}
    │◄────────────────────────────────── {"valid": true}
    │  2. GET /api/v1/medoc/export/42/
    │◄────────────────────────────────── ZIP (windows-1251 XML)
    ▼ Користувач зберігає ZIP
    ▼ Імпорт у M.E.Doc → Створення податкової накладної → Відправка до ДПС

або:

    │  GET /api/v1/medoc/cloud/42/
    │◄────────────────────────────────── JSON payload
    ▼ Локальний агент / СОТА інтеграція → Автоматична відправка

Статус та плани розвитку

Функціональність Статус
Довідник УКТЗЕД (модель + admin + search API) ✅ реалізовано
Поле uktzed_code на товарі ✅ реалізовано
Валідація перед експортом ✅ реалізовано
Генерація J1203001 XML (видаткова, юр. особа) ✅ реалізовано
Генерація F1203001 XML (видаткова, ФОП) ✅ реалізовано
Упаковка в ZIP-архів ✅ реалізовано
Cloud/JSON endpoint для агента ✅ реалізовано
Перевірка ліцензії через App Store ✅ реалізовано
Management command load_uktzed ✅ реалізовано
Модель TaxInvoice + lifecycle ЄРПН 🧪 experimental (2026-04-22) — див. попередження
Генерація J1201006 XML (податкова накладна) 🧪 experimental (2026-04-22)
Імпорт вхідних XML → авто-створення TaxInvoice (direction=in) 🧪 experimental (2026-04-22)
Звірка з ЄРПН (CSV → 4 buckets) 🧪 experimental (2026-04-22)
TaxInvoiceViewSet (CRUD + from_invoice/xml/mark_registered) 🧪 experimental (2026-04-22) — backend only, UI немає
Tax accounting в DOP як окремий модуль 🔮 deferred — «колись, може, зробимо»
Поля region_code, district_code на Organization ⏳ не реалізовано
Уточнюючий документ / розрахунок коригування (adjustment) ⏳ не реалізовано
Кілька документів в одному ZIP ⏳ не реалізовано
Frontend UI кнопка «Експорт до M.E.Doc» + TaxInvoice list ⏳ не реалізовано
Підпис ЕЦП (КЕП) через локальний агент ⏳ не реалізовано
Автоматична відправка через СОТА API ⏳ не реалізовано