⚖️ Ukraine
Updated
The LocalizationUkraine model, Customization layer. Sales, Tax and
Purchasing know nothing about Ukraine: the package sits on registers of its own
and on extensions of other people's documents.
← Localizations · User: Ukrainian taxes · Analyst: the package scope · M.E.Doc · PrivatBank · Diia
Pages#
- VAT (ПДВ) and the regimes — why the tax is computed by a
totals driver and not by the document; why a single-tax payer
(спрощенець) exemption is set through
TaxMappingand not by anif (regime)branch inside Tax; the personal income tax (ПДФО) and military levy (військовий збір) withholdings from payroll and thePayrollLiability.Withheldresource through which a withholder declares itself. - Unified Register of Tax Invoices (ЄРПН) — the
TaxDocumentenvelope through an operator, the submission queue and a separate channel for the question about the receipt; why a successful send does NOT mean registration, and why "sent, no answer" is resolved by a query and not by a second submission. There is no cloud M.E.Doc sandbox. - PrivatBank statement — Autoclient v3 GET, channel
pb-statementsonUaBankConnection, registerUaBankStatement. Payments are not created from the statement. There is no Autoclient sandbox; Open Banking is a different API. - Diia — login is not in the country package; there is no stub channel (no agreement with the Ministry of Digital Transformation). Diia does not replace the tax invoice.
What is worth knowing before reading the code#
Three things in this package are arranged differently from what intuition suggests:
- The tax is not accrued by the document. The liability is born of the first event — the shipment or the money (ПКУ 187.1) — and the documents only report the event into a register. That is why the accrual lives in a totals driver and not in a posting entry.
- The regime is a property of the legal entity, not of the system. A limited
liability company (ТОВ) on VAT (ПДВ) and several sole proprietors (ФОП) on
single tax (єдиний податок) are accounted for in one database. The
UaTaxRegimefield sits on theLegalEntityextension, does not reach the generated class, is read out of the bag and written as the enum's number. - Rates and codes are data, not constants. The military levy (ВЗ) rate has already changed (1.5% → 5% from 2024-12-01), the income limit changes every year along with the minimum wage, and the State Tax Service (ДПС) form number depends on the version of the form. None of those numbers is hardcoded into a script.
Amount in words#
IUaAmountInWords.Hryvnias(decimal) writes a hryvnia amount the way a payment
invoice and a reconciliation act print it. Use it instead of writing your own:
| Amount | Text |
|---|---|
| 1 | Одна гривня 00 копійок |
| 21.01 | Двадцять одна гривня 01 копійка |
| 1 234.56 | Одна тисяча двісті тридцять чотири гривні 56 копійок |
| 3 022 000.40 | Три мільйони двадцять дві тисячі гривень 40 копійок |
The rules it follows:
- Hryvnia and thousand are feminine (одна, дві). Million and billion are masculine (один, два).
- The noun form follows the number: 1 takes one form, 2–4 another, the rest a third. 11–14 always take the third form.
- Kopecks are printed as two digits.
- The amount is first rounded to the kopeck, half away from zero.
The service is a plain function: it reads no data and writes none. The test is
UaAmountInWordsTest. The Okrasheno client model uses it on its payment invoice
for a sales order (OkrashenoSalesOrderPaymentInvoiceZr).
Same topic for other readers: Analyst