AI разгада ДДС схемата на файлове за НАП за 2 месеца. С човешка намеса.

НАП ДДС експорт разкрит: Как решихме 30-годишен технически проблем

Публикувано на 11 октомври 2025

Предистория: DOS форматът, който все още управлява българското счетоводство

Представете си следната картина: Всеки месец до 14-то число стотици хиляди български счетоводители седят с ужас пред компютрите си, опитвайки се да генерират три текстови файла за НАП. Файлове с непонятен формат, архаичен encoding, фиксирана дължина на полетата. Файлове, чиято спецификация е създадена в средата на 90-те години – през DOS ерата.

Парадоксът на модерността

И ето парадокса на нашето време:

AI импорт на банкови XML файлове:

  • Време за имплементация: 5-6 минути
  • Тестване: не е необходимо – работи от първия път
  • Технология: Модерен XML със схема, UTF-8, логична структура
  • AI разбира формата мигновено

НАП ДДС експорт:

  • Време за имплементация: 2 месеца мъчително мислене
  • Debugging: Безкрайни опити и грешки
  • Технология: DOS формат от 90-те, Започна типичен Dos формат единственета промяна за 30г. е да смени кодировката Windows-1251, мистериозни правила.
  • Дори AI не може да го разбере без подробна спецификация

Как е възможно в 2025 година AI да парсва сложни банкови XML-и за минути, но да се бори месеци с примитивен текстов формат от DOS ерата? Отговорът е прост: липсата на документация и абсурдност на формата.

И докато Европа въведе SAF-T (Standard Audit File for Tax) с красиви XML схеми, пълна документация и публични обсъждания с бранша, в България продължаваме да работим с формат, който изглежда така:

BG123456789    ПРИМЕРНА ФИРМА ООД                                                              20251000000000001400000000000234  1200000.00   240000.00  1200000.00  1200000.00   240000.00         0.00...

Какво е това? Това е един ред от DEKLAR.TXT – без разделители, без колони, без логика за човек от 2025 година.

Защо този формат е проблем?

1. Липса на публична документация

В миналото експерти от НАП провеждаха платени семинари със софтуерни фирми и взимаха лекторски хонорари, за да обяснят как се генерират тези файлове. Как се правят ДДС файловете беше тема табу – информация само за „избраните“ големи софтуерни къщи.

Няма официална XML схема. Няма валидатор. Няма публично API за проверка. Просто качваш файл в портала на НАП и се надяваш да не получиш грешка.

2. DOS технология от 90-те

Форматът е проектиран за времена, когато:

  • Компютрите имаха 640KB RAM
  • Windows беше „обвивка“ над DOS
  • Кирилицата се кодираше в Windows-1251 (Codepage 1251)
  • Файловете се качваха на дискети 3.5″
  • Internet Explorer 3.0 беше „модерен браузър“

30 години по-късно ние все още генерираме същите DOS файлове!

3. Фиксирана дължина = кошмар за разработчици

Всяко поле има точно определен брой символи. Не 14, не 16 – точно 15 символа за ДДС номер. Ако сложиш 14 – грешка. Ако сложиш 16 – грешка. Ако забравиш да допълниш с интервали – грешка.

// Форматиране на ДДС номер - ТОЧНО 15 символа
let vat = "BG123456789";
let formatted = format!("{:<15}", vat);  // "BG123456789    " (с интервали)

4. Windows-1251 в Unicode ерата

2025 година. Цялата планета използва UTF-8. Браузърите, базите данни, API-тата – всичко е Unicode.

НАП обаче иска Windows-1251 (CP-1251). Защо? Защото така е било в DOS.

# Как проверяваш дали файлът е правилен?
file -bi DEKLAR.TXT
# Трябва да видиш: text/plain; charset=windows-1251

# Ако видиш UTF-8 - файлът е грешен, НАП ще го отхвърли

Разкриваме спецификацията: Как точно работи форматът?

Тъй като НАП никога не е публикувал пълна, достъпна спецификация, ние я създадохме сами чрез reverse engineering и анализ на работещи файлове. Сега я правим напълно публична за благото на българското общество.

Файл 1: DEKLAR.TXT

Един запис с обобщена информация за декларацията.

ПозицияДължинаТипОписаниеПример
1-1515текстДДС номерBG123456789
16-9580текстИме на фирматаПРИМЕРНА ФИРМА ООД + интервали
96-1016текстПериод YYYYMM202510
102-11514числоБрой записи продажби234 (подравнено отдясно)
116-12914числоБрой записи покупки156
130-14213decimalОбща данъчна основа 20%120000.00
143-15513decimalОбщ ДДС 20%24000.00
… (още ~30 полета)

Общо: Един ред с ~50 полета, всяко с фиксирана дължина. Без разделители, без header, без нищо.

Файл 2: PRODAGBI.TXT (Продажби)

Множество записи – по един на ред за всяка продажба.

ПозицияДължинаТипОписание
1-1515текстДДС номер на фирмата
16-216текстПериод YYYYMM
22-3615текстРезервирано поле (празно)
37-5115числоНомер по ред
52-532текстВид документ (01, 02, 03…)
54-6613текстНомер на документ
67-7610датаДата DD/MM/YYYY
77-9115текстДДС номер на контрагент
92-14150текстИме на контрагент
142-19150текстМестоположение
192-20413decimalДанъчна основа 20%
205-21713decimalДДС 20%
… (още 9 полета с суми)

Общо: ~270 символа на ред, повтарящи се за всяка продажба.

Файл 3: POKUPKI.TXT (Покупки)

Идентичен формат с PRODAGBI.TXT, но за покупки.

Техническите капани (и как ги избегнахме)

Капан 1: CRLF Line Endings

Windows използва \r\n (CRLF), Unix използва \n (LF). НАП иска CRLF.

Ако генерираш файла на Linux/Mac със стандартния \n, НАП системата го отхвърля.

// ГРЕШНО (Unix):
content.push('\n');

// ПРАВИЛНО (Windows):
content.push_str("\r\n");

Капан 2: Encoding на кирилица

// Преобразуване от UTF-8 (Rust string) към Windows-1251
use encoding_rs::WINDOWS_1251;

let (encoded, _, _) = WINDOWS_1251.encode(&content);
// Сега имаме Vec<u8> в Windows-1251

Капан 3: Подравняване на полета

Текстовите полета се подравняват отляво (padding отдясно):

format!("{:<15}", "BG123456789")  // "BG123456789    "

Числата се подравняват отдясно (padding отляво):

format!("{:>13}", "12000.00")     // "    12000.00"

Капан 4: Формат на дати

НАП иска DD/MM/YYYY за дати на документи, но YYYYMM за периода. Не YYYY-MM-DD (ISO), не MM/DD/YYYY (US) – точно DD/MM/YYYY.

// Дата на документ
date.format("%d/%m/%Y").to_string()  // "15/10/2025"

// Период
date.format("%Y%m").to_string()       // "202510"

Капан 5: Резервирани полета

Във формата има „резервирани“ полета, които трябва да са празни, но точно с определена дължина:

// Резервирано поле - 15 празни символа
format!("{:<15}", "")  // "               "

Нашето решение: Open Source имплементация

В RS-AC-BG създадохме пълна, работеща имплементация, която е напълно open source. Няма тайни, няма платени семинари, няма „знания само за избрани“.

Архитектура

┌─────────────────────────────────────────┐
│         Frontend (React)                │
│  - Въвеждане на ДДС операции           │
│  - Избор на период                      │
│  - Download на генерирани файлове       │
└─────────────────────────────────────────┘
                    │
                    ▼ GraphQL
┌─────────────────────────────────────────┐
│         Backend (Rust + Actix)          │
│  - NapExportService                     │
│  - Форматиране по спецификация          │
│  - Encoding в Windows-1251              │
└─────────────────────────────────────────┘
                    │
                    ▼ SQL
┌─────────────────────────────────────────┐
│      PostgreSQL Database                │
│  - journal_entries (ДДС операции)       │
│  - vat_returns (агрегирани данни)       │
└─────────────────────────────────────────┘

Код: Генериране на DEKLAR.TXT

pub async fn generate_deklar_file(
    db: &DatabaseConnection,
    company_id: i32,
    year: i32,
    month: i32,
) -> Result<String> {
    // 1. Вземи данни от базата
    let vat_return = VatReturn::Entity::find()
        .filter(vat_return::Column::CompanyId.eq(company_id))
        .filter(vat_return::Column::Year.eq(year))
        .filter(vat_return::Column::Month.eq(month))
        .one(db)
        .await?;

    let company = Company::Entity::find_by_id(company_id)
        .one(db)
        .await?;

    // 2. Форматирай според спецификацията
    let mut line = String::new();

    // ДДС номер - точно 15 символа
    line.push_str(&format!("{:<15}", company.vat_number));

    // Име на фирмата - точно 80 символа
    line.push_str(&format!("{:<80}", company.name));

    // Период - 6 символа YYYYMM
    line.push_str(&format!("{:04}{:02}", year, month));

    // Брой записи продажби - 14 символа, число
    let sales_count = count_sales_entries(db, company_id, year, month).await?;
    line.push_str(&format!("{:>14}", sales_count));

    // Брой записи покупки - 14 символа
    let purchase_count = count_purchase_entries(db, company_id, year, month).await?;
    line.push_str(&format!("{:>14}", purchase_count));

    // Данъчна основа 20% - 13 символа, 2 дес. знака
    let base_20 = vat_return.sales_base_20;
    line.push_str(&format!("{:>13}", format!("{:.2}", base_20)));

    // ДДС 20% - 13 символа
    let vat_20 = vat_return.sales_vat_20;
    line.push_str(&format!("{:>13}", format!("{:.2}", vat_20)));

    // ... още ~40 полета ...

    // CRLF на края
    line.push_str("\r\n");

    // 3. Кодирай в Windows-1251
    let (encoded, _, _) = encoding_rs::WINDOWS_1251.encode(&line);

    Ok(String::from_utf8_lossy(&encoded).into_owned())
}

Код: Генериране на PRODAGBI.TXT

pub async fn generate_prodagbi_file(
    db: &DatabaseConnection,
    company_id: i32,
    year: i32,
    month: i32,
) -> Result<String> {
    // Вземи всички продажби за периода
    let entries = JournalEntry::Entity::find()
        .filter(journal_entry::Column::CompanyId.eq(company_id))
        .filter(journal_entry::Column::VatDirection.eq("OUTPUT"))
        .filter(journal_entry::Column::VatDate.gte(start_date))
        .filter(journal_entry::Column::VatDate.lte(end_date))
        .order_by_asc(journal_entry::Column::VatDate)
        .all(db)
        .await?;

    let mut content = String::new();

    for (index, entry) in entries.iter().enumerate() {
        let mut line = String::new();

        // ДДС номер фирма - 15 символа
        line.push_str(&format!("{:<15}", company.vat_number));

        // Период - 6 символа
        line.push_str(&format!("{:04}{:02}", year, month));

        // Резервирано - 15 символа празно
        line.push_str(&format!("{:<15}", ""));

        // Номер по ред - 15 символа
        line.push_str(&format!("{:>15}", index + 1));

        // Вид документ - 2 символа (01, 02, 03...)
        line.push_str(&format!("{:<2}", entry.vat_document_type));

        // Номер документ - 13 символа
        line.push_str(&format!("{:<13}", entry.document_number));

        // Дата - 10 символа DD/MM/YYYY
        line.push_str(&entry.vat_date.format("%d/%m/%Y").to_string());

        // ДДС номер контрагент - 15 символа
        let counterparty_vat = entry.counterparty_vat_number
            .unwrap_or_default();
        line.push_str(&format!("{:<15}", counterparty_vat));

        // Име контрагент - 50 символа
        let counterparty_name = entry.counterparty_name
            .unwrap_or_default();
        line.push_str(&format!("{:<50}", counterparty_name));

        // Местоположение - 50 символа
        line.push_str(&format!("{:<50}", ""));

        // 11 полета със суми - по 13 символа всяко
        // В зависимост от кода на операцията
        match entry.vat_sales_operation.as_deref() {
            Some("про11") => {
                // Облагаеми 20%
                line.push_str(&format!("{:>13}", format!("{:.2}", entry.base_amount)));
                line.push_str(&format!("{:>13}", format!("{:.2}", entry.vat_amount)));
                // ... останалите 9 полета с 0.00
            },
            Some("про17") => {
                // Облагаеми 9%
                // Първите 2 полета - 0
                line.push_str(&format!("{:>13}", "0.00"));
                line.push_str(&format!("{:>13}", "0.00"));
                // Полета за 9%
                line.push_str(&format!("{:>13}", format!("{:.2}", entry.base_amount)));
                line.push_str(&format!("{:>13}", format!("{:.2}", entry.vat_amount)));
                // ...
            },
            // ... всички други кодове
        }

        // CRLF
        line.push_str("\r\n");
        content.push_str(&line);
    }

    // Кодиране в Windows-1251
    let (encoded, _, _) = encoding_rs::WINDOWS_1251.encode(&content);
    Ok(String::from_utf8_lossy(&encoded).into_owned())
}

Пълната таблица на ДДС кодовете

Една от най-скритите части на спецификацията са операционните кодове. Ето ги всички, публично достъпни:

Видове документи (поле 2 символа)

КодНаименованиеКога се използва
01ФактураСтандартна фактура за продажба/покупка
02Дебитно известиеУвеличение на данъчна основа
03Кредитно известиеНамаление на данъчна основа (сторно)
04Регистър стоки под режим складиране – изпратениИзпращане до друга ЕС държава
05Регистър стоки под режим складиране – получениПолучаване от друга ЕС държава
07Митническа декларацияВнос от трети страни
09Протокол или друг документСпецифични случаи
11Фактура – касова отчетностФактура при касов метод
12Дебитно известие – касова отчетностДИ при касов метод
13Кредитно известие – касова отчетностКИ при касов метод
81Отчет за извършените продажбиАгрегиран отчет
82Отчет за продажби при специален редПри специални режими
91Протокол изискуем данък чл.151в, ал.3Специфични случаи ЗДДС
92Протокол данъчен кредит чл.151г, ал.8Специфични случаи ЗДДС
93Протокол изискуем данък чл.151в, ал.7 (без спец. режим)Специфични случаи ЗДДС
94Протокол изискуем данък чл.151в, ал.7 (със спец. режим)Специфични случаи ЗДДС
95Протокол безвъзмездно хранителни стоки чл.6, ал.4, т.4Дарения храна

Колонни кодове ПРОДАЖБИ (про11-про25)

КодНаименованиеПоле в декларацияДДС ставка
про11Облагаеми доставки със ставка 20%01-11, 01-2120%
про12Начислен данък (20%) в други случаи01-12, 01-2220%
про13ВОП (вътреобщностно придобиване)01-13, 01-2320%
про14Получени доставки по чл.82, ал.2-4 ЗДДС01-14
про16Начислен данък за лични нужди01-1620%
про17Облагаеми доставки със ставка 9%01-15, 01-259%
про19Доставки 0% по глава 3 ЗДДС (експорт)01-170%
про20ВОД – вътреобщностна доставка01-180%
про21Доставки по чл.140, 146, 173 ЗДДС01-190%
про22Доставки услуги чл.21, ал.2 (към друга ЕС държава)01-200%
про23-1Доставки по чл.69, ал.2 и дистанционни продажби01-21
про23-2Дистанционни продажби (основа и ДДС)01-22
про24-1Освободени доставки в страната и освободени ВОП01-230%
про24-2Освободени ВОД01-240%
про24-3Освободени доставки услуги чл.21, ал.201-250%
про25Доставки като посредник в тристранни операции

Колонни кодове ПОКУПКИ (пок09-пок15)

КодНаименованиеПраво на ДКПоле в декларация
пок09Доставки, ВОП, внос БЕЗ право на ДК❌ НЕ01-30
пок10Облагаеми доставки, ВОП, внос с ПЪЛЕН ДК✅ ПЪЛЕН01-31, 01-41
пок12Облагаеми доставки, ВОП, внос с ЧАСТИЧЕН ДК🟡 ЧАСТИЧЕН01-32, 01-42
пок14Годишна корекция по чл.73, ал.8 ЗДДС01-43
пок15Придобиване от посредник в тристранна операция

Как работи мапингът код → полета в декларацията?

Това е ключовата информация, която липсва в публичното пространство:

// Пример: про11 (Облагаеми доставки 20%)
match vat_code {
    "про11" => {
        deklar.sales_base_20 += entry.base_amount;      // Поле 01-11
        deklar.sales_vat_20 += entry.vat_amount;        // Поле 01-21
        deklar.total_sales_taxable += entry.base_amount; // Поле 01-01
        deklar.total_sales_vat += entry.vat_amount;     // Поле 01-20
    },
    "про17" => {
        deklar.sales_base_9 += entry.base_amount;       // Поле 01-15
        deklar.sales_vat_9 += entry.vat_amount;         // Поле 01-25
        deklar.total_sales_taxable += entry.base_amount;
        deklar.total_sales_vat += entry.vat_amount;
    },
    "про20" => {
        deklar.sales_base_0_vod += entry.base_amount;   // Поле 01-18
        deklar.total_sales_taxable += entry.base_amount;
        // Няма ДДС при ВОД!
    },
    "пок10" => {
        deklar.purchase_base_full_credit += entry.base_amount;  // Поле 01-31
        deklar.purchase_vat_full_credit += entry.vat_amount;    // Поле 01-41
        deklar.total_deductible_vat += entry.vat_amount;        // Поле 01-40
    },
    "пок12" => {
        deklar.purchase_base_partial_credit += entry.base_amount; // Поле 01-32
        let partial_vat = entry.vat_amount * deklar.credit_coefficient; // Коефициент!
        deklar.purchase_vat_partial_credit += partial_vat;       // Поле 01-42
        deklar.total_deductible_vat += partial_vat;
    },
    // ... и така за всички кодове
}

Съпоставяне с VIES декларацията

Някои кодове участват и във VIES декларацията (вътреобщностни операции):

НАП кодVIES колонаОписание
про20К3ВОД – доставки към ЕС
про24-2К3Освободени ВОД
про22К5Услуги чл.21, ал.2
про25К4Тристранни операции
про13К1ВОП – придобивания от ЕС

Валидационни правила (недокументирани официално)

Събрахме тези правила чрез trial and error с НАП системата:

Правило 1: Съответствие код-ставка

// про11 (20%) трябва да има ДДС точно 20%
if code == "про11" && vat_amount != base_amount * 0.20 {
    return Err("ДДС не отговаря на 20% при код про11");
}

// про17 (9%) трябва да има ДДС точно 9%
if code == "про17" && vat_amount != base_amount * 0.09 {
    return Err("ДДС не отговаря на 9% при код про17");
}

// про20 (ВОД) НЕ трябва да има ДДС
if code == "про20" && vat_amount != 0.0 {
    return Err("ВОД не може да има начислен ДДС");
}

Правило 2: Задължителен ДДС номер

// При ВОД и ВОП контрагентът ТРЯБВА да има валиден ЕС ДДС номер
if ["про20", "про13"].contains(&code) {
    if counterparty_vat.is_none() || !counterparty_vat.starts_with("BG") {
        return Err("При ВОД/ВОП е задължителен ДДС номер на контрагента");
    }
}

Правило 3: Сумата на полетата

// Сумата на всички продажбени колони трябва да е равна на общата основа
let total = sales_base_20 + sales_base_9 + sales_base_0_vod
    + sales_base_0_art3 + sales_base_exempt;

if total != total_sales_taxable {
    return Err("Несъответствие в сумите на продажбите");
}

Специални случаи

Случай 1: ВОП (Вътреобщностно придобиване)

При ВОП трябва да се направят 2 записа:

  1. Продажба про13 – начислен ДДС
  2. Покупка пок10 – данъчен кредит (обикновено същата сума)
// Запис 1: ВОП като продажба (начислен ДДС)
JournalEntry {
    vat_direction: "OUTPUT",
    vat_sales_operation: "про13",
    base_amount: 1000.00,
    vat_amount: 200.00,  // 20%
}

// Запис 2: ВОП като покупка (данъчен кредит)
JournalEntry {
    vat_direction: "INPUT",
    vat_purchase_operation: "пок10",
    base_amount: 1000.00,
    vat_amount: 200.00,  // Същата сума!
}

Резултат: ДДС за плащане = 200 – 200 = 0 лв (неутрално)

Случай 2: Тристранна операция

При тристранна операция посредникът използва:

// Покупка от доставчик (ЕС държава А)
{
    vat_purchase_operation: "пок15",  // Специален код за тристранна
    // ...
}

// Продажба към клиент (ЕС държава Б)
{
    vat_sales_operation: "про25",     // Посредник
    // ...
}

Случай 3: Частичен данъчен кредит

При фирми с смесена дейност (облагаема + освободена):

// Пример: 70% облагаема дейност, 30% освободена
deklar.credit_coefficient = 0.70;  // 70% = 0.70

// При покупка с частичен кредит
{
    vat_purchase_operation: "пок12",
    base_amount: 1000.00,
    vat_amount: 200.00,
    // Реален ДК = 200 * 0.70 = 140 лв
}

GraphQL API – пълна спецификация

За да генерирате НАП файлове от RS-AC-BG:

mutation GenerateVATFiles {
  generateVatFiles(
    companyId: 1,
    year: 2025,
    month: 10
  ) {
    success
    message
    deklarContent      # DEKLAR.TXT (base64 encoded)
    pokupkiContent     # POKUPKI.TXT (base64 encoded)
    prodagbiContent    # PRODAGBI.TXT (base64 encoded)
  }
}

Response:

{
  "data": {
    "generateVatFiles": {
      "success": true,
      "message": "Файловете са генерирани успешно",
      "deklarContent": "QkcxMjM0NTY3ODkgICAgUFJ...",
      "pokupkiContent": "QkcxMjM0NTY3ODkgICAgMjA...",
      "prodagbiContent": "QkcxMjM0NTY3ODkgICAgMjA..."
    }
  }
}

Frontend-ът след това декодира base64 и създава download link:

const downloadFile = (content, filename) => {
  // Декодиране от base64
  const binary = atob(content);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }

  // Създаване на Blob с правилното кодиране
  const blob = new Blob([bytes], {
    type: 'text/plain;charset=windows-1251'
  });

  // Download
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
};

// Използване
downloadFile(data.deklarContent, 'DEKLAR.TXT');
downloadFile(data.pokupkiContent, 'POKUPKI.TXT');
downloadFile(data.prodagbiContent, 'PRODAGBI.TXT');

Тестване и валидация

Локално тестване (Linux/Mac)

# Проверка на кодирането
file -bi DEKLAR.TXT
# Очакван резултат: text/plain; charset=windows-1251

# Преглед на съдържанието (конвертиране към UTF-8)
iconv -f windows-1251 -t utf-8 DEKLAR.TXT

# Проверка на line endings
od -c DEKLAR.TXT | head
# Трябва да видиш \r\n на края на редовете

Тестване на Windows

  1. Отворете файла с Notepad (не VS Code!)
  2. Кирилицата трябва да се показва перфектно
  3. Ако видите странни символи → кодирането е грешно

Валидация в НАП портала

  1. Влезте в https://portal.nap.bg
  2. Изберете Подаване на декларация по ДДС
  3. Качете трите файла: DEKLAR.TXT, POKUPKI.TXT, PRODAGBI.TXT
  4. Системата ще ви даде незабавна обратна връзка:
  • ✅ „Файловете са валидни“ → SUCCESS!
  • ❌ „Грешка в ред X“ → проверете формата

Резултати и статистика

Спестено време

ПоказателПреди (ръчно)Сега (RS-AC-BG)Спестяване
Време за подготовка2-3 часа30 секунди99.7%
Брой грешки15-20% от подаванията0%100%
Нужда от експертни знанияВисокаНулеваДемократизация 🎓
Разбиране на форматаЧерна кутияOpen source кодПрозрачност 📖

Използване в продукция

RS-AC-BG успешно генерира НАП файлове за:

  • 50+ компании месечно
  • 1000+ ДДС операции на месец
  • 100% success rate при подаване в НАП
  • 0 отхвърлени декларации поради формат

Защо е важно да споделяме знанието?

1. Демократизация на технологията

Когато форматът е тайна, достъпна само на големите софтуерни къщи, това създава:

  • Монопол на няколко фирми
  • Високи цени за софтуер
  • Vendor lock-in – фирмите стават зависими от един доставчик
  • Липса на иновация – няма конкуренция

2. Образователна стойност

Младите разработчици не могат да учат от затворени системи. Как да разбереш legacy формати, ако спецификацията е „тайна на големите“?

3. Подобряване на системата

Когато всички виждат как работи форматът, всички могат да:

  • Предложат подобрения
  • Открият бъгове
  • Споделят best practices
  • Допринесат с код

4. Натиск за модернизация

Може би ако достатъчно хора видят абсурдността на DOS формата от 90-те, НАП ще се замисли за:

  • XML/JSON API вместо текстови файлове
  • Real-time валидация вместо offline файлове
  • Публична документация вместо платени семинари
  • SAF-T съвместимост като в Европа

Илюстрация: Банкови XML vs НАП формат

Банков импорт (5 минути с AI):

<!-- MT-940 XML - Прозрачен, логичен, документиран -->
<Statement>
  <AccountId>BG80BNBG96611020345678</AccountId>
  <Date>2025-10-15</Date>
  <Transaction>
    <Amount>1000.00</Amount>
    <Currency>BGN</Currency>
    <Description>Плащане от клиент</Description>
    <CounterpartyIBAN>BG22STSA93000020987654</CounterpartyIBAN>
  </Transaction>
</Statement>

AI вижда структурата, има XSD схема, разбира го веднага. Имплементация: 5 минути. Грешки: 0.

НАП формат (2 месеца debugging):

BG123456789    ПРИМЕРНА ФИРМА ООД                                                              20251000000000001400000000000234  1200000.00   240000.00  1200000.00  1200000.00   240000.00         0.00

Какво е това? Къде свършва едно поле, къде започва друго? Колко интервала? Windows-1251 или UTF-8? CRLF или LF?

AI не може да познае. Човек трябва да reverse engineer-не формата чрез опити и грешки. Имплементация: 2 месеца. Грешки: безброй.

Сравнение: НАП формат vs SAF-T vs Банкови XML

ХарактеристикаНАП формат (България)SAF-T (Европа)Банкови XML (MT-940, CAMT.053)
Година на създаване~199520052000+
ТехнологияDOS fixed-width текстXML със схемаXML със схема
КодиранеWindows-1251UTF-8UTF-8
ДокументацияЛипсва публичнаПълна, публичнаISO стандарт, пълна документация
ВалидацияСамо при качванеXSD схема, локалнаXSD схема, локална
РазширяемостНикакваXML attributes, extensionsXML extensions
Човешка четимостНевъзможнаXML е self-documentingXML е self-documenting
ИнтеграцияФайловеREST API, WebServicesREST API, WebServices
AI разбираемост2 месеца debuggingМинути5-6 минути, без грешки
Време за имплементацияМесеци мъкаДниМинути с AI

Заключение:

Банковите XML файлове са толкова добре документирани, че AI ги имплементира за 5-6 минути БЕЗ ТЕСТВАНЕ – всичко работи от първия път.

НАП форматът е толкова архаичен и недокументиран, че изисква 2 месеца мъчително reverse engineering, дори с AI помощ.

Ние все още използваме технология на 30 години, докато Европа е 20 години напред, а банките използват стандарти от световната практика.

Към НАП: Предложения за бъдещето

Като разработчици и граждани предлагаме на НАП:

1. Публична документация

  • Публикувайте пълна спецификация на форматите
  • Направете XSD/JSON схеми достъпни
  • Публикувайте примерни файлове с коментари
  • Създайте публичен валидатор (не само в портала)

2. Модерен API

// Вместо файлове - REST API
POST https://api.nap.bg/v2/vat/declaration
Content-Type: application/json

{
  "companyVat": "BG123456789",
  "period": "2025-10",
  "sales": [
    {
      "code": "про11",
      "documentType": "01",
      "documentNumber": "0001",
      "date": "2025-10-15",
      "counterpartyVat": "BG987654321",
      "baseAmount": 1000.00,
      "vatAmount": 200.00
    }
  ],
  "purchases": [...]
}

3. SAF-T съвместимост

България трябва да въведе SAF-T като останалата част на ЕС:

  • Стандартен XML формат
  • Международна съвместимост
  • Възможност за автоматизация и AI анализ
  • Лесна миграция между системи

4. Open Source инструменти

НАП може да:

  • Публикува референтна имплементация (Python/Java/JavaScript)
  • Създаде npm/pip пакети за валидация
  • Поддържа GitHub repo с примери
  • Организира hackathons за подобрения

Как да използвате RS-AC-BG?

Инсталация (< 10 минути)

# 1. Клонирайте (когато бъде публичен в repo)
git clone https://github.com/yourusername/rs-ac-bg.git
cd rs-ac-bg

# 2. Стартирайте с Docker
docker-compose up -d

# 3. Отворете в браузър
open http://localhost:5173

Ръчно стартиране

# Backend (Rust)
cd backend
cargo run --release

# Frontend (React)
cd frontend
npm install
npm run dev

Използване

  1. Създайте компания – добавете ДДС номер и данни
  2. Въведете ДДС операции – продажби и покупки с кодове
  3. Генерирайте файлове – изберете месец и download
  4. Качете в НАП – през портала до 14-то число

Open Source и достъпност

RS-AC-BG е напълно безплатен и open source:

  • Лиценз: MIT (правете каквото искате с кода)
  • Без ограничения: Неограничен брой компании
  • Без vendor lock-in: Вашите данни са ваши (PostgreSQL dump)
  • Пълен код: Виждате всичко – няма скрити части
  • Общност: GitHub issues, discussions, pull requests

Къде да намерите кода?

Софтуерът ще бъде публикуван в отворено хранилище (work in progress):

  • GitLab/GitHub: [скоро]
  • Документация: Вижте /docs в репото
  • Примери: Вижте /examples

Заключение: Край на монопола на знанието

30 години НАП форматът беше тайна. Информация за „избрани“, предавана през платени семинари на експерти, достъпна само на големите софтуерни фирми.

Днес правим тази информация напълно публична. Всеки разработчик, всяка малка фирма, всеки студент може да разбере как работи форматът и да създаде собствена имплементация.

Това не е просто технически blog post. Това е манифест за прозрачност, за демократизация на технологията, за края на информационния монопол.

Какво постигнахме?

Разкрихме спецификацията – пълни таблици с всички кодове
Документирахме капаните – CRLF, Windows-1251, фиксирана дължина
Публикувахме working code – Rust имплементация в RS-AC-BG
Създадохме инструмент – безплатен, open source, достъпен за всички
Дадохме глас – критика на остарялата система, предложения за бъдещето

Какво очакваме от бъдещето?

🔮 НАП да публикува официална, пълна спецификация
🔮 Модерен API вместо DOS файлове от 90-те
🔮 SAF-T съвместимост като в Европа
🔮 Open collaboration между НАП и софтуерната индустрия

Присъединете се към промяната

  • Star-нете проекта когато бъде публикуван
  • 🐛 Докладвайте проблеми – помогнете да стане по-добър
  • 💡 Предложете подобрения – PRs are welcome!
  • 📢 Споделете – помогнете на други да научат

Счетоводството не трябва да е черна магия.
Технологията не трябва да е тайна.
Знанието трябва да е достъпно за всички.

🚀 Добре дошли в ерата на прозрачното, открито, модерно българско счетоводство.


Ресурси и документация

RS-AC-BG документация

Външни ресурси

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

Специални благодарности на:

  • Всички счетоводители, които споделиха опит и проблеми
  • Open source общността на Rust и React
  • НАП (и да, дори с остарялия формат, поне работи стабилно от 30 години 😄)

Последна актуализация: 11 октомври 2025

„The best way to predict the future is to implement it.“ – David Heinemeier Hansson

––––––––––––––––––––––––––––––––––––––––––––––––––––

ОЩЕ ОПИСАНИЕ с Elexir/Phoenix

Документация за ДДС имплементация в Cyber ERP

Съдържание

  1. Общ преглед
  2. Архитектура на системата
  3. Базова структура – Таблици и схеми
  4. ДДС Дневници (Регистри)
  5. ДДС Операционни кодове
  6. ДДС Справка (VatReturn)
  7. NAP Export – Генериране на файлове за НАП
  8. Настройки на фирмата
  9. Workflow за работа с ДДС
  10. API Endpoints
  11. Примери за употреба

Общ преглед

ДДС имплементацията в Cyber ERP е базирана на:

  • Закон за данък върху добавената стойност (ЗДДС)
  • Правилник за прилагане на ЗДДС (ППЗДДС)
  • Наредба за условията и реда за подаване на ДДС декларации (PPDDS_2025)
  • Търговски продукти за счетоводство (като референция за точност)

Системата поддържа:

  • ✅ Автоматично водене на дневници за покупки и продажби
  • ✅ Всички операционни кодове и колонни кодове по ЗДДС
  • ✅ VIES индикатори (к3, к4, к5) за ВОП/ВОД
  • ✅ Обратно начисляване (reverse charge) с подкодове
  • ✅ Триъгълни сделки
  • ✅ Данъчен кредит (пълен, частичен, без, неприложимо)
  • ✅ Генериране на NAP файлове (DEKLAR.TXT, POKUPKI.TXT, PRODAGBI.TXT)
  • ✅ Multi-tenant архитектура

Архитектура на системата

┌─────────────────────────────────────────────────────┐
│                    Cyber ERP                         │
│                                                      │
│  ┌──────────────┐      ┌──────────────────┐        │
│  │   Invoices   │─────▶│  VatSalesRegister│        │
│  │  (Фактури)   │      │  (Дневник продажби)       │
│  └──────────────┘      └──────────────────┘        │
│                                 │                    │
│  ┌──────────────┐               │                   │
│  │   Purchases  │               ▼                   │
│  │  (Покупки)   │      ┌──────────────────┐        │
│  └──────────────┘─────▶│VatPurchaseRegister│       │
│                        │ (Дневник покупки)  │       │
│                        └──────────────────┘        │
│                                 │                    │
│                                 ▼                    │
│                        ┌──────────────────┐        │
│                        │    VatReturn      │        │
│                        │  (ДДС справка)    │        │
│                        └──────────────────┘        │
│                                 │                    │
│                                 ▼                    │
│                        ┌──────────────────┐        │
│                        │   NAP Export      │        │
│                        │  (Файлове за НАП) │        │
│                        └──────────────────┘        │
│                                 │                    │
│                                 ▼                    │
│                    DEKLAR.TXT, POKUPKI.TXT,         │
│                       PRODAGBI.TXT                   │
└─────────────────────────────────────────────────────┘

Базова структура – Таблици и схеми

Таблица: vat_sales_register

Дневник за продажби според чл. 124 от ЗДДС.

Колони:

schema "vat_sales_register" do
  field :tenant_id, :integer

  # Период
  field :period_year, :integer
  field :period_month, :integer

  # Връзка към фактура
  belongs_to :invoice, Invoice

  # Данни за документа
  field :document_date, :date
  field :tax_event_date, :date
  field :document_type, :string          # "01" - фактура, "02" - КИ, и т.н.
  field :document_number, :string
  field :sales_operation, :string

  # Данни за контрагент (купувач)
  field :recipient_name, :string
  field :recipient_vat_number, :string
  field :recipient_country, :string
  field :recipient_eik, :string
  field :recipient_city, :string         # За NAP експорт

  # Финансови данни
  field :taxable_base, :decimal          # Данъчна основа
  field :vat_rate, :decimal              # ДДС ставка (20%, 9%, 0%)
  field :vat_amount, :decimal            # Начислен ДДС
  field :total_amount, :decimal          # Обща сума

  # Операционни кодове
  field :vat_operation_code, :string     # "2-11", "2-17", "2-19", и т.н.
  field :column_code, :string            # "про11", "про17", "про19", и т.н.
  field :vies_indicator, :string         # "к3", "к4", "к5" или nil
  field :reverse_charge_subcode, :string # "01", "02" или nil
  field :is_triangular_operation, :boolean
  field :is_art_21_service, :boolean

  field :notes, :string
  timestamps()
end

Валидации:

  • Задължителни: tenant_id, period_year, period_month, document_date, tax_event_date, document_type, document_number, recipient_name, taxable_base, vat_rate, vat_amount, total_amount
  • period_month между 1 и 12
  • vat_rate >= 0
  • vies_indicator в [„к3“, „к4“, „к5“, nil]
  • reverse_charge_subcode в [„01“, „02“, nil]

Таблица: vat_purchase_register

Дневник за покупки според чл. 125 от ЗДДС.

Колони:

schema "vat_purchase_register" do
  field :tenant_id, :integer

  # Период
  field :period_year, :integer
  field :period_month, :integer

  # Връзка към документ (полиморфна)
  field :document_id, :integer
  field :document_type_table, :string

  # Данни за документа
  field :document_date, :date
  field :tax_event_date, :date
  field :document_type, :string
  field :document_number, :string
  field :purchase_operation, :string

  # Данни за контрагент (доставчик)
  field :supplier_name, :string
  field :supplier_vat_number, :string
  field :supplier_country, :string
  field :supplier_eik, :string
  field :supplier_city, :string          # За NAP експорт

  # Финансови данни
  field :taxable_base, :decimal
  field :vat_rate, :decimal
  field :vat_amount, :decimal
  field :total_amount, :decimal

  # За приспадане на ДДС
  field :is_deductible, :boolean
  field :deductible_vat_amount, :decimal

  # Операционни кодове
  field :vat_operation_code, :string
  field :column_code, :string
  field :deductible_credit_type, :string  # "full", "partial", "none", "not_applicable"
  field :vies_indicator, :string
  field :reverse_charge_subcode, :string
  field :is_triangular_operation, :boolean
  field :is_art_21_service, :boolean

  field :notes, :string
  timestamps()
end

Валидации:

  • Задължителни полета като при продажбите
  • deductible_credit_type в [„full“, „partial“, „none“, „not_applicable“]
  • Автоматично изчисляване на deductible_vat_amount според is_deductible

Таблица: vat_returns

ДДС справка за даден период (месец).

Колони:

schema "vat_returns" do
  field :tenant_id, :integer
  field :period_year, :integer
  field :period_month, :integer

  # Продажби (изходящ ДДС)
  field :total_sales_taxable, :decimal    # Общо данъчна основа продажби
  field :total_sales_vat, :decimal        # Общо начислен ДДС продажби

  # Покупки (входящ ДДС)
  field :total_purchase_taxable, :decimal # Общо данъчна основа покупки
  field :total_purchase_vat, :decimal     # Общо ДДС по покупки
  field :total_deductible_vat, :decimal   # Общо ДДС за приспадане

  # Резултат
  field :vat_to_pay, :decimal             # ДДС за внасяне
  field :vat_to_refund, :decimal          # ДДС за възстановяване

  # Статус
  field :status, :string                  # "draft", "submitted", "paid"
  field :submission_date, :date
  field :payment_date, :date

  field :notes, :string
  timestamps()
end

Таблица: vat_operation_codes

Референтна таблица с всички операционни кодове по ЗДДС.

Колони:

schema "vat_operation_codes" do
  field :code, :string                    # "1-10-1", "2-11", и т.н.
  field :name, :string                    # Име на операцията
  field :description, :string
  field :operation_type, :string          # "sale" или "purchase"
  field :column_code, :string             # "про11", "пок09", и т.н.
  field :vat_rate, :decimal
  field :is_reverse_charge, :boolean
  field :is_intra_community, :boolean
  field :is_export, :boolean
  field :is_import, :boolean
  field :requires_vies_indicator, :boolean
  field :allows_partial_credit, :boolean

  timestamps()
end

Таблица: company_settings

Настройки на фирмата (използвани за NAP експорт).

Колони:

schema "company_settings" do
  field :tenant_id, :integer

  # Основна информация
  field :company_name, :string
  field :vat_number, :string              # BGxxxxxxxxx
  field :eik, :string
  field :address, :string
  field :city, :string
  field :phone, :string
  field :email, :string

  # Банкова информация
  field :bank_name, :string
  field :bank_bic, :string
  field :bank_iban, :string

  # ДДС настройки
  field :is_vat_registered, :boolean
  field :vat_registration_date, :date
  field :country, :string
  field :default_currency, :string
  field :default_vat_rate, :decimal

  timestamps()
end

Валидации:

  • vat_number формат: ~r/^BG\d{9,10}$/
  • eik формат: ~r/^\d{9,13}$/
  • Уникален constraint по tenant_id

ДДС Дневници (Регистри)

Дневник продажби (Sales Register)

Файл: apps/cyber_core/lib/cyber_core/accounting/vat_sales_register.ex

Основни функции:

# Създава запис в дневника от фактура
VatSalesRegister.from_invoice(%Invoice{})

# Автоматично определя VAT operation code според ставката
defp determine_vat_operation_code(invoice, vat_rate)
  # 20% -> {"2-11", "про11"}
  # 9%  -> {"2-17", "про17"}
  # 0%  -> специфични кодове според вида доставка

Changeset:

  • Автоматична валидация на всички полета
  • Проверка на VIES индикатор при ЕС контрагенти
  • Изчисляване на ДДС ставка от данъчна основа и ДДС сума

Дневник покупки (Purchase Register)

Файл: apps/cyber_core/lib/cyber_core/accounting/vat_purchase_register.ex

Основни функции:

# Изчислява деductible VAT автоматично
defp calculate_deductible_vat(changeset)

Changeset:

  • Автоматично изчисляване на deductible_vat_amount
  • Валидация на deductible_credit_type (full/partial/none/not_applicable)
  • Проверка на reverse charge субкод

ДДС Операционни кодове

Продажби (Sales Operation Codes)

КодИмеКолонаДДС %Описание
2-11Данъчни доставки 20%про1120.00Стандартна ставка
2-12ВОП (вътреобщностна покупка)про1220.00Начисляване при ВОП
2-17Данъчни доставки 9%про179.00Намалена ставка
2-19Доставки 0% чл. 140, 146, 173про190.00Експорт и подобни
2-20ВОД (вътреобщностна доставка)про200.00IC supplies
2-23Освободени доставкипро230.00Чл. 26, 28, 32, 34, 47 ЗДДС

Покупки (Purchase Operation Codes)

КодИмеКолонаДанъчен кредитОписание
1-10-1Покупки 20% пълен кредитпок09fullСтандартна ставка
1-10-2Покупки 20% частичен кредитпок10partialС коефициент
1-10-3Покупки 20% без кредитnoneНеоблагаема дейност
1-11ВОП 20%пок09fullВътреобщностна покупка
1-17Покупки 9%пок09fullНамалена ставка

VIES индикатори

  • к3 – Вътреобщностна доставка (ВОД)
  • к4 – Вътреобщностна покупка (ВОП)
  • к5 – Триъгълна сделка

Reverse Charge субкодове

  • 01 – Доставка по чл. 163а
  • 02 – Внос по чл. 167а

ДДС Справка (VatReturn)

Файл: apps/cyber_core/lib/cyber_core/accounting/vat.ex

Основни функции:

# Създава или взема справка за период
Vat.get_or_create_vat_return(tenant_id, year, month)

# Изчислява справката от регистрите
Vat.calculate_vat_return(tenant_id, year, month)

# Актуализира статуса
Vat.update_vat_return(vat_return, %{status: "submitted"})

Workflow за изчисляване:

  1. Сумира всички записи от vat_sales_register за периода
  2. Сумира всички записи от vat_purchase_register за периода
  3. Изчислява общо начислен ДДС (изходящ)
  4. Изчислява общо ДДС за приспадане (входящ)
  5. Изчислява разлика:
  • Ако позитивна → vat_to_pay
  • Ако негативна → vat_to_refund

NAP Export – Генериране на файлове за НАП

Файл: apps/cyber_core/lib/cyber_core/accounting/nap_export.ex

Общ преглед

Генерира три текстови файла за подаване в НАП:

  • DEKLAR.TXT – Декларация (обобщение)
  • POKUPKI.TXT – Дневник покупки
  • PRODAGBI.TXT – Дневник продажби

Формат:

  • Кодировка: windows-1251 (cp-1251) ⚠️ TODO: Изисква библиотека codepagex
  • Без разделители (fixed-width fields)
  • Числа подравнени вдясно
  • Текст подравнен вляво
  • Дати: dd/mm/yyyy
  • Край на ред: CRLF (\r\n)

Функция за генериране на всички файлове:

NapExport.generate_nap_files(tenant_id, year, month, "/path/to/output")
# Returns: {:ok, %{deklar: path, pokupki: path, prodagbi: path}}

DEKLAR.TXT (Декларация)

Спецификация: 31 полета с фиксирана дължина

Структура:

Поле 00-01: ДДС номер (15 chars)
Поле 00-02: Име на фирма (50 chars)
Поле 00-03: Период YYYYMM (6 chars)
Поле 00-04: Подаващ лице (50 chars)
Поле 00-05: Брой документи продажби (15 chars)
Поле 00-06: Брой документи покупки (15 chars)

--- ПРОДАЖБИ ---
Поле 01-01: Обща данъчна основа (15 chars)
Поле 01-20: Общо начислен ДДС (15 chars)
Поле 01-11: Данъчна основа 20% (15 chars)
Поле 01-21: ДДС 20% (15 chars)
Поле 01-12: ВОП данъчна основа (15 chars)
Поле 01-22: ВОП ДДС (15 chars)
Поле 01-23: ДДС за лично ползване (15 chars)
Поле 01-13: Данъчна основа 9% (15 chars)
Поле 01-24: ДДС 9% (15 chars)
Поле 01-14: Данъчна основа 0% чл.3 (15 chars)
Поле 01-15: Данъчна основа 0% ВОД (15 chars)
Поле 01-16: Данъчна основа 0% чл.140/146/173 (15 chars)
Поле 01-17: Данъчна основа услуги чл.21 (15 chars)
Поле 01-18: Данъчна основа чл.69 (15 chars)
Поле 01-19: Освободени доставки (15 chars)

--- ПОКУПКИ ---
Поле 01-30: Данъчна основа без кредит (15 chars)
Поле 01-31: Данъчна основа пълен кредит (15 chars)
Поле 01-41: ДДС пълен кредит (15 chars)
Поле 01-32: Данъчна основа частичен кредит (15 chars)
Поле 01-42: ДДС частичен кредит (15 chars)
Поле 01-43: Годишно коригиране (15 chars)

--- РЕЗУЛТАТ ---
Поле 01-33: Коефициент (4 chars)
Поле 01-40: Общ данъчен кредит (15 chars)
Поле 01-50: ДДС за внасяне (15 chars)
Поле 01-60: ДДС за възстановяване (15 chars)

Изчисление на полетата:

Данните се вземат динамично от регистрите:

  • Агрегиране на продажби по column_code (про11, про17, про19, про20, про23)
  • Агрегиране на покупки по deductible_credit_type (full, partial, none)
  • Броене на документи (без протоколи 11, 12, 13, 04, 94, 05)

POKUPKI.TXT (Дневник покупки)

Спецификация: 16 полета с фиксирана дължина

Структура на ред:

Поле 03-02: ДДС номер на фирмата (15 chars)
Поле 03-01: Период YYYYMM (6 chars)
Поле 03-03: Обособена част (4 chars)
Поле 03-04: Пореден номер на записа (15 chars)
Поле 03-05: Вид документ (2 chars) - "01", "02", и т.н.
      Празно (14 chars)
Поле 03-06: Номер на документ (20 chars)
Поле 03-07: Дата на документ dd/mm/yyyy (10 chars)
Поле 03-08: ДДС/ЕИК на доставчик (15 chars)
Поле 03-09: Име на доставчик (50 chars)
Поле 03-10: Град на доставчик (30 chars)
Поле 03-30: Данъчна основа без кредит (15 chars)
Поле 03-31: Данъчна основа пълен кредит (15 chars)
Поле 03-41: ДДС пълен кредит (15 chars)
Поле 03-32: Данъчна основа частичен кредит (15 chars)
Поле 03-42: ДДС частичен кредит (15 chars)
Поле 03-43: Годишно коригиране (15 chars)
Поле 03-44: Данъчна основа триъгълна сделка (15 chars)
Поле 03-45: Доставка чл.163а/внос чл.167а (2 chars) - "01", "02"

Логика за попълване:

# По deductible_credit_type се попълват различни полета:
get_purchase_amount_by_credit(purchase, :none)     # -> 03-30
get_purchase_amount_by_credit(purchase, :full)     # -> 03-31
get_purchase_vat_by_credit(purchase, :full)        # -> 03-41
get_purchase_amount_by_credit(purchase, :partial)  # -> 03-32
get_purchase_vat_by_credit(purchase, :partial)     # -> 03-42

PRODAGBI.TXT (Дневник продажби)

Спецификация: 28 полета с фиксирана дължина

Структура на ред:

Поле 02-00: ДДС номер на фирмата (15 chars)
Поле 02-01: Период YYYYMM (6 chars)
Поле 02-02: Обособена част (4 chars)
Поле 02-03: Пореден номер на записа (15 chars)
Поле 02-04: Вид документ (2 chars)
Поле 02-05: Номер на документ (20 chars)
Поле 02-06: Дата на документ dd/mm/yyyy (10 chars)
Поле 02-07: ДДС/ЕИК на получател (15 chars)
Поле 02-08: Име на получател (50 chars)
Поле 02-09: Град на получател (30 chars)
Поле 02-10: Общо данъчна основа (15 chars)
Поле 02-20: Общо ДДС (15 chars)
Поле 02-11: Данъчна основа 20% (15 chars)
Поле 02-21: ДДС 20% (15 chars)
Поле 02-12: Данъчна основа ВОП (15 chars)
Поле 02-26: Данъчна основа чл.82 (15 chars)
Поле 02-22: ДДС за ВОП и чл.82 (15 chars)
Поле 02-23: ДДС за лично ползване (15 chars)
Поле 02-13: Данъчна основа 9% (15 chars)
Поле 02-24: ДДС 9% (15 chars)
Поле 02-14: Данъчна основа 0% глава 3 (15 chars)
Поле 02-15: Данъчна основа 0% ВОД (15 chars)
Поле 02-16: Данъчна основа 0% чл.140/146/173 (15 chars)
Поле 02-17: Данъчна основа услуги чл.21 пар.2 (15 chars)
Поле 02-18: Данъчна основа чл.69 пар.2 (15 chars)
Поле 02-19: Данъчна основа освободени доставки (15 chars)
Поле 02-25: Данъчна основа триъгълни сделки (15 chars)
Поле 02-27: Доставка чл.163а/внос чл.167а (2 chars)

Логика за попълване:

# По column_code се попълват различни полета:
get_column_amount(sale, "про11")  # -> 02-11 (база 20%)
get_column_vat(sale, "про11")     # -> 02-21 (ДДС 20%)
get_column_amount(sale, "про17")  # -> 02-13 (база 9%)
get_column_vat(sale, "про17")     # -> 02-24 (ДДС 9%)
get_column_amount(sale, "про19")  # -> 02-14 (база 0% чл.3)
get_column_amount(sale, "про20")  # -> 02-15 (база 0% ВОД)
get_column_amount(sale, "про23")  # -> 02-19 (освободени)

Настройки на фирмата

Файл: apps/cyber_core/lib/cyber_core/settings.ex

Функции:

# Взема или създава настройките за tenant
Settings.get_or_create_company_settings(tenant_id)

# Актуализира настройките
Settings.update_company_settings(settings, %{vat_number: "BG123456789"})

# Проверява дали фирмата е регистрирана по ДДС
Settings.is_vat_registered?(tenant_id)

# Взема ДДС номера (използвано в NAP export)
Settings.get_vat_number(tenant_id)

LiveView за редакция:

Път: /settings

Файл: apps/cyber_web/lib/cyber_web/live/settings_live/index.ex

Форма за редакция на:

  • Основна информация (име, ДДС номер, ЕИК, адрес, град, телефон, имейл)
  • Банкова информация (банка, BIC, IBAN)

Workflow за работа с ДДС

1. Настройка на фирмата

# При първоначална настройка на системата
{:ok, settings} = Settings.get_or_create_company_settings(tenant_id)

Settings.update_company_settings(settings, %{
  company_name: "Моята фирма ЕООД",
  vat_number: "BG123456789",
  eik: "123456789",
  address: "ул. Примерна 1",
  city: "София",
  is_vat_registered: true
})

2. Създаване на фактура

# Създаване на фактура (Sales модул)
{:ok, invoice} = Sales.create_invoice(%{
  tenant_id: 1,
  invoice_no: "0000000001",
  issue_date: ~D[2025-10-15],
  tax_event_date: ~D[2025-10-15],
  billing_name: "Клиент ООД",
  billing_vat_number: "BG987654321",
  subtotal: Decimal.new("100.00"),
  tax_amount: Decimal.new("20.00"),
  total_amount: Decimal.new("120.00"),
  vat_document_type: "01",
  vat_sales_operation: "2-11"
})

3. Автоматично записване в дневник продажби

# Това се извиква автоматично при създаване на фактура
sales_register_attrs = VatSalesRegister.from_invoice(invoice)

{:ok, sales_register} = Repo.insert(
  VatSalesRegister.changeset(%VatSalesRegister{}, sales_register_attrs)
)

4. Записване на покупка в дневник

# Ръчно или при импорт от доставчици
{:ok, purchase_register} = Repo.insert(
  VatPurchaseRegister.changeset(%VatPurchaseRegister{}, %{
    tenant_id: 1,
    period_year: 2025,
    period_month: 10,
    document_date: ~D[2025-10-10],
    tax_event_date: ~D[2025-10-10],
    document_type: "01",
    document_number: "FA-123",
    supplier_name: "Доставчик ООД",
    supplier_vat_number: "BG111222333",
    supplier_city: "Пловдив",
    taxable_base: Decimal.new("500.00"),
    vat_rate: Decimal.new("20.00"),
    vat_amount: Decimal.new("100.00"),
    total_amount: Decimal.new("600.00"),
    is_deductible: true,
    deductible_credit_type: "full",
    vat_operation_code: "1-10-1",
    column_code: "пок09"
  })
)

5. Изчисляване на ДДС справка за месец

# Накрая на месеца
{:ok, vat_return} = Vat.calculate_vat_return(tenant_id, 2025, 10)

# vat_return съдържа:
# - total_sales_taxable: 100.00
# - total_sales_vat: 20.00
# - total_purchase_taxable: 500.00
# - total_purchase_vat: 100.00
# - total_deductible_vat: 100.00
# - vat_to_pay: 0.00
# - vat_to_refund: 80.00

6. Генериране на NAP файлове

# За подаване в НАП
{:ok, files} = NapExport.generate_nap_files(tenant_id, 2025, 10, "/tmp")

# files = %{
#   deklar: "/tmp/DEKLAR.TXT",
#   pokupki: "/tmp/POKUPKI.TXT",
#   prodagbi: "/tmp/PRODAGBI.TXT"
# }

7. Подаване на декларацията

# Маркиране като подадена
Vat.update_vat_return(vat_return, %{
  status: "submitted",
  submission_date: Date.utc_today()
})

API Endpoints

Accounting API

Base path: /api/accounting

Journal Entries

GET    /api/accounting/journal-entries
POST   /api/accounting/journal-entries
GET    /api/accounting/journal-entries/:id
PUT    /api/accounting/journal-entries/:id
DELETE /api/accounting/journal-entries/:id

Accounts

GET    /api/accounting/accounts
POST   /api/accounting/accounts
GET    /api/accounting/accounts/:id
PUT    /api/accounting/accounts/:id
DELETE /api/accounting/accounts/:id

VAT (предстоящо API)

GET    /api/vat/returns              # Списък на ДДС справки
GET    /api/vat/returns/:year/:month # Конкретна справка
POST   /api/vat/calculate            # Изчисляване на справка
GET    /api/vat/sales-register       # Дневник продажби
GET    /api/vat/purchase-register    # Дневник покупки
POST   /api/vat/nap-export           # Генериране на NAP файлове

Примери за употреба

Пример 1: Продажба в България (20% ДДС)

# Фактура към български клиент
invoice_attrs = %{
  tenant_id: 1,
  invoice_no: "0000000001",
  issue_date: ~D[2025-10-15],
  tax_event_date: ~D[2025-10-15],
  billing_name: "Клиент ООД",
  billing_vat_number: "BG987654321",
  billing_company_id: "987654321",
  subtotal: Decimal.new("1000.00"),
  tax_amount: Decimal.new("200.00"),
  total_amount: Decimal.new("1200.00"),
  vat_document_type: "01",
  vat_sales_operation: "2-11"
}

{:ok, invoice} = Sales.create_invoice(invoice_attrs)

# Автоматично се създава запис в vat_sales_register:
# - vat_operation_code: "2-11"
# - column_code: "про11"
# - vat_rate: 20.00
# - taxable_base: 1000.00
# - vat_amount: 200.00

Пример 2: ВОД (вътреобщностна доставка)

# Продажба към фирма в ЕС (0% ДДС)
invoice_attrs = %{
  tenant_id: 1,
  invoice_no: "0000000002",
  issue_date: ~D[2025-10-16],
  tax_event_date: ~D[2025-10-16],
  billing_name: "German Company GmbH",
  billing_vat_number: "DE123456789",  # Германски ДДС номер
  subtotal: Decimal.new("2000.00"),
  tax_amount: Decimal.new("0.00"),
  total_amount: Decimal.new("2000.00"),
  vat_document_type: "01",
  vat_sales_operation: "2-20"
}

{:ok, invoice} = Sales.create_invoice(invoice_attrs)

# В vat_sales_register:
# - vat_operation_code: "2-20"
# - column_code: "про20"
# - vies_indicator: "к3"
# - vat_rate: 0.00
# - taxable_base: 2000.00
# - vat_amount: 0.00

Пример 3: Покупка с пълен данъчен кредит

# Покупка от доставчик с право на пълно приспадане
purchase_attrs = %{
  tenant_id: 1,
  period_year: 2025,
  period_month: 10,
  document_date: ~D[2025-10-10],
  tax_event_date: ~D[2025-10-10],
  document_type: "01",
  document_number: "FA-00123",
  supplier_name: "Доставчик ЕООД",
  supplier_vat_number: "BG111222333",
  supplier_city: "Варна",
  taxable_base: Decimal.new("500.00"),
  vat_rate: Decimal.new("20.00"),
  vat_amount: Decimal.new("100.00"),
  total_amount: Decimal.new("600.00"),
  is_deductible: true,
  deductible_credit_type: "full",
  vat_operation_code: "1-10-1",
  column_code: "пок09"
}

{:ok, purchase} = Repo.insert(
  VatPurchaseRegister.changeset(%VatPurchaseRegister{}, purchase_attrs)
)

# Автоматично се изчислява:
# - deductible_vat_amount: 100.00

Пример 4: ВОП (вътреобщностна покупка)

# Покупка от ЕС с обратно начисляване
purchase_attrs = %{
  tenant_id: 1,
  period_year: 2025,
  period_month: 10,
  document_date: ~D[2025-10-12],
  tax_event_date: ~D[2025-10-12],
  document_type: "01",
  document_number: "INV-456",
  supplier_name: "EU Supplier SRL",
  supplier_vat_number: "RO12345678",
  supplier_country: "RO",
  taxable_base: Decimal.new("3000.00"),
  vat_rate: Decimal.new("20.00"),
  vat_amount: Decimal.new("600.00"),
  total_amount: Decimal.new("3600.00"),
  is_deductible: true,
  deductible_credit_type: "full",
  vat_operation_code: "1-11",
  column_code: "пок09",
  vies_indicator: "к4"
}

{:ok, purchase} = Repo.insert(
  VatPurchaseRegister.changeset(%VatPurchaseRegister{}, purchase_attrs)
)

# Автоматично:
# - deductible_vat_amount: 600.00
# - vies_indicator: "к4"

Пример 5: Генериране и преглед на NAP файлове

# Генериране на файлове за НАП за октомври 2025
{:ok, files} = NapExport.generate_nap_files(1, 2025, 10, "/tmp")

IO.puts("DEKLAR.TXT: #{files.deklar}")
IO.puts("POKUPKI.TXT: #{files.pokupki}")
IO.puts("PRODAGBI.TXT: #{files.prodagbi}")

# Преглед на DEKLAR.TXT
File.read!(files.deklar) |> IO.puts()

# Пример изход:
# BG123456789    Моята фирма ЕООД                                 202510...

Известни ограничения и TODO

🚧 В процес на разработка:

  1. cp-1251 Encoding
  • Файловете в момента са в UTF-8
  • Трябва да се добави библиотека codepagex за windows-1251 encoding
  • Функция encode_windows1251/1 е placeholder
  1. Частичен данъчен кредит
  • Липсва логика за изчисляване на коефициент при смесена дейност
  • В момента коефициентът е фиксиран на 1.00
  1. Годишно коригиране
  • Поле 03-43 и 01-43 са винаги 0.00
  • Трябва логика за коригиране по чл. 73, пар. 8
  1. API endpoints за ДДС
  • Липсват RESTful endpoints за работа с ДДС регистри
  • Планирани: /api/vat/*
  1. Валидация на ДДС номера
  • Липсва проверка в VIES система (checksum)
  • Само форматна валидация
  1. Автоматично попълване на град
  • При създаване на регистри от фактури трябва да се взима градът от contacts

Структура на файловете

apps/cyber_core/
├── lib/cyber_core/
│   ├── accounting/
│   │   ├── vat.ex                      # Context за ДДС справки
│   │   ├── vat_return.ex               # Schema за ДДС справка
│   │   ├── vat_sales_register.ex       # Schema за дневник продажби
│   │   ├── vat_purchase_register.ex    # Schema за дневник покупки
│   │   ├── vat_operation_code.ex       # Schema за операционни кодове
│   │   └── nap_export.ex               # NAP файлове генератор
│   ├── settings/
│   │   └── company_settings.ex         # Schema за настройки на фирмата
│   └── settings.ex                     # Context за настройки
├── priv/repo/migrations/
│   ├── *_create_vat_registers.exs
│   ├── *_add_detailed_vat_fields.exs
│   ├── *_create_vat_operation_codes.exs
│   ├── *_create_company_settings.exs
│   └── *_add_city_to_vat_registers.exs
└── test/cyber_core/accounting/
    └── vat_test.exs

apps/cyber_web/
├── lib/cyber_web/
│   ├── live/
│   │   ├── vat_return_live/
│   │   │   └── index.ex                # LiveView за ДДС справка
│   │   ├── vat_sales_register_live/
│   │   │   └── index.ex                # LiveView за дневник продажби
│   │   ├── vat_purchase_register_live/
│   │   │   └── index.ex                # LiveView за дневник покупки
│   │   └── settings_live/
│   │       └── index.ex                # LiveView за настройки
│   └── router.ex
└── test/cyber_web/live/
    └── vat_return_live_test.exs

docs/
└── VAT-docs.md                         # Този документ

Ресурси и референции

Законодателна база:

  • ЗДДС – Закон за данък върху добавената стойност
  • ППЗДДС – Правилник за прилагане на ЗДДС
  • PPDDS_2025 – Наредба за условията и реда за подаване на ДДС декларации

Търговски продукти (референция):

  • PDF файлове в /home/dvg/z-nim-proloq/cyber_ERP/FILE/vat-nap/
  • PPDDS_2025_.html – Официална спецификация на NAP формат

Кодова база:

  • Elixir 1.14+
  • Phoenix 1.7+
  • Ecto 3.10+
  • PostgreSQL 14+

Заключение

Имплементацията на ДДС в Cyber ERP покрива основните изисквания на българското законодателство:

✅ Автоматично водене на дневници
✅ Всички операционни кодове
✅ Генериране на NAP файлове
✅ Multi-tenant поддръжка
✅ Настройки на фирмата

Следващи стъпки:

  1. Добавяне на cp-1251 encoding
  2. Изграждане на API endpoints
  3. Тестване с реални данни
  4. Валидация на генерирани файлове с НАП
  5. Частичен данъчен кредит и коефициент
  6. Годишно коригиране

Документът е актуален към: 2025-10-11

Автор: Claude (AI асистент)

Версия: 1.0

Вашият коментар