openapi: 3.1.0
info:
  title: Szamlify.hu API
  version: "1.0"
  description: |
    A Szamlify.hu publikus REST API-ja: partnerek, termékek és bizonylatok
    (számla, díjbekérő, árajánlat, szállítólevél, teljesítési igazolás,
    munkalap) programozott kezelése.

    Ez a fájl (`https://szamlify.hu/api-docs/openapi.yaml`) a TELJES, gépi
    olvasásra szánt dokumentáció — AI-asszisztensnek, kódgenerátornak vagy
    Postmannek közvetlenül átadható. Az interaktív változat:
    `https://szamlify.hu/api/docs`. Alap-URL: `https://szamlify.hu/api/v1`.

    ## Hitelesítés

    Minden kérést az API-kulccsal kell hitelesíteni:

    ```
    Authorization: Bearer szmf_live_...
    ```

    API-kulcsot a vállalkozás-részletoldal **API-kulcsok** fülén hozhatsz
    létre. A kulcsból következik a vállalkozás — minden művelet automatikusan
    a kulcs cégére vonatkozik, `company_id`-t nem kell (és nem is lehet) küldeni.

    ## Sandbox mód

    A kulcs létrehozásakor választható a mód:
    - `szmf_test_...` — **sandbox** kulcs: minden művelet egy különálló
      teszt-adatbázison fut. A bizonylatok valódi felépítésű sorszámot kapnak
      (külön teszt-számlálóból), NAV-adatszolgáltatás nincs, a kimenő
      e-mailek tárgya `[TESZT]` jelzést kap, a PDF-eken „TESZT BIZONYLAT"
      vízjel van.
    - `szmf_live_...` — **éles** kulcs: teljes értékű működés (valódi
      sorszám, NAV-adatszolgáltatás, valódi e-mail).

    ## Csomag-korlátok

    Az **éles** API használatához aktív Standard vagy Pro előfizetés
    szükséges — enélkül az éles kulccsal indított kérések `403`-at adnak
    (a sandbox kulcsokat ez nem érinti). A bizonylat-létrehozás és a
    számla-kiállítás a csomag havi bizonylat-keretét fogyasztja; a keret
    elérésekor a végpontok `422`-t adnak magyar hibaüzenettel. A sztornó
    és módosító számla nem fogyaszt keretet.

    Az integráció fejlesztését mindig sandbox kulccsal kezdd!

    ## Korlátok

    - **120 kérés / perc / kulcs** — felette `429 Too Many Requests`
      (a `Retry-After` fejléc mondja meg, mennyit kell várni).
    - A listák lapozottak: `page` és `per_page` (5–100, alapérték 25)
      paraméterekkel; a válasz a Laravel lapozó-formátumát követi
      (`data`, `total`, `current_page`, `last_page`, …).

    ## Hibák

    - `401` — hiányzó vagy érvénytelen API-kulcs
    - `404` — nem létező, más vállalkozáshoz vagy másik módhoz (sandbox/éles)
      tartozó erőforrás
    - `422` — validációs hiba; a `errors` objektum mezőnként adja a magyar
      hibaüzeneteket
    - `423` — a fiókra törlési igény van rögzítve: a türelmi idő alatt minden
      író művelet zárolt (olvasás továbbra is működik)
servers:
  - url: /api/v1
security:
  - apiKey: []
tags:
  - name: Kapcsolat
  - name: Partnerek
  - name: Termékek
  - name: Számlák
  - name: Díjbekérők
  - name: Árajánlatok
  - name: Szállítólevelek
  - name: Teljesítési igazolások
  - name: Munkalapok
  - name: NAV-ból szinkronizált költség számlák
  - name: Törzsadatok

paths:
  /me:
    get:
      tags: [Kapcsolat]
      summary: A kulcs ellenőrzése
      description: Visszaadja a kulcshoz tartozó vállalkozást és a kulcs módját — integráció-teszthez.
      responses:
        "200":
          description: OK
          content:
            application/json:
              example:
                company: { id: 4, company_name: "Cloudify Informatika Kft.", tax_number: "27527474-2-15" }
                api_key: { name: "Webshop", mode: "sandbox" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /usage:
    get:
      tags: [Kapcsolat]
      summary: Havi API-bizonylatkeret fogyasztása
      description: >
        Az aktuális naptári hónapban az ÉLES API-n kiállított bizonylatok száma a
        fiók összes éles kulcsára összesítve, az ingyenes kerettel és a keret feletti
        (díjköteles) darabszámmal együtt. A felületen (böngészőben) kiállított
        bizonylatok NEM számítanak bele, ahogy a sztornó/módosító számla és a
        sandbox-forgalom sem.


        A Pro csomagban havi 100 bizonylat a csomag árában van, a fölötte lévő
        darabok díja nettó 1,5 Ft + ÁFA, amit a hónap végén, utólag számlázunk.
        Nettó 1 000 Ft alatti összeget nem számlázunk ki: az átgörgetődik a
        következő hónapra. Ha a csomagnak nincs külön API-kerete
        (`metered: false`), az API-n kiállított bizonylat a csomag havi
        bizonylat-keretét fogyasztja.
      responses:
        "200":
          description: OK
          content:
            application/json:
              example:
                month: "2026-08"
                metered: true
                documents_used: 127
                free_quota: 100
                billable_documents: 27
                unit_price_net: 1.5
                estimated_net_amount: 40.5
                currency: "HUF"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /partners:
    get:
      tags: [Partnerek]
      summary: Partnerek listája
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés a partner nevében" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott partnerlista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedPartners" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Partnerek]
      summary: Partner létrehozása
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PartnerInput" }
      responses:
        "201":
          description: A létrehozott partner
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Partner" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /partners/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Partnerek]
      summary: Partner lekérése
      responses:
        "200":
          description: A partner
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Partner" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Partnerek]
      summary: Partner módosítása
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PartnerInput" }
      responses:
        "200":
          description: A módosított partner
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Partner" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Partnerek]
      summary: Partner törlése
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }

  /products:
    get:
      tags: [Termékek]
      summary: Termékek listája
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés a termék nevében" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott terméklista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedProducts" }
    post:
      tags: [Termékek]
      summary: Termék létrehozása
      description: A megnevezés vállalkozáson belül egyedi. Az ár nettó egységár, a vállalkozás alapértelmezett pénznemében (Ft).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProductInput" }
      responses:
        "201":
          description: A létrehozott termék
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Product" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /products/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Termékek]
      summary: Termék lekérése
      responses:
        "200":
          description: A termék
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Product" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Termékek]
      summary: Termék módosítása
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProductInput" }
      responses:
        "200":
          description: A módosított termék
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Product" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Termékek]
      summary: Termék törlése
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }

  /invoices:
    get:
      tags: [Számlák]
      summary: Számlák listája
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra vagy partner névre" }
        - { name: status, in: query, schema: { type: string, enum: [draft, issued] }, description: "Státusz-szűrő" }
        - { name: date_from, in: query, schema: { type: string, format: date }, description: "Kelte-tartomány kezdete" }
        - { name: date_to, in: query, schema: { type: string, format: date }, description: "Kelte-tartomány vége" }
        - { name: fulfillment_from, in: query, schema: { type: string, format: date }, description: "Teljesítés dátuma tartomány kezdete" }
        - { name: fulfillment_to, in: query, schema: { type: string, format: date }, description: "Teljesítés dátuma tartomány vége" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott számlalista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
    post:
      tags: [Számlák]
      summary: Számla-piszkozat létrehozása
      description: |
        A piszkozat is teljes, érvényes adatot követel; az összegeket a
        szerver számolja a tételekből (`price_mode` szerint nettó vagy
        bruttó egységárból). A sorszámot, keltét és esedékességet a
        **kiállítás** rögzíti. Banki átutalás fizetési módnál a
        `bank_account_id` kötelező.

        **Másik bizonylatból (díjbekérő, árajánlat, szállítólevél,
        teljesítési igazolás, munkalap):** add meg a `source_type` +
        `source_id` párost. A tételeket ilyenkor is NEKED kell beküldeni —
        a hivatkozás annyit tesz, hogy kiállításkor a forrás „Számlázva"
        jelölést kap, és a számla PDF-jén megjelenik, mi alapján készült.

        **Végszámla:** `document_kind: final`, és a tételek között
        ÁFA-kulcsonként egy NEGATÍV mennyiségű sor `advance_invoice_id`-vel,
        a kiállított előlegszámlára hivatkozva (egységára az előleg
        nettója). Egy előlegszámla csak egyszer számítható be.

        A lenti legördülőből válaszd ki a folyamathoz tartozó példát.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InvoiceInput" }
            examples:
              normal:
                summary: Egyszerű számla
                value:
                  partner_name: "Minta Vevő Kft."
                  partner_country_id: 75
                  partner_postal_code: "1052"
                  partner_city: "Budapest"
                  partner_address: "Váci utca 10."
                  partner_tax_type: "has_tax_number"
                  partner_tax_number: "12345678-2-42"
                  partner_emails: ["penzugy@mintavevo.hu"]
                  fulfillment_date: "2026-07-17"
                  payment_method_id: 1
                  payment_deadline_days: 8
                  bank_account_id: 3
                  invoice_block_id: 5
                  language_id: 1
                  currency_id: 1
                  price_mode: "net"
                  items:
                    - { name: "Szolgáltatás", quantity: 2, unit_id: 2, unit_price: 10000, vat_rate_id: 1 }
              from_source:
                summary: Számla díjbekérőből (source_type + source_id)
                description: |
                  Ugyanígy megy árajánlatból (`quotation`), szállítólevélből
                  (`delivery_note`), teljesítési igazolásból
                  (`completion_certificate`) és munkalapból (`work_sheet`) is.
                value:
                  source_type: "fee_request"
                  source_id: 12
                  partner_name: "Minta Vevő Kft."
                  partner_country_id: 75
                  partner_postal_code: "1052"
                  partner_city: "Budapest"
                  partner_address: "Váci utca 10."
                  partner_tax_type: "has_tax_number"
                  partner_tax_number: "12345678-2-42"
                  partner_emails: ["penzugy@mintavevo.hu"]
                  fulfillment_date: "2026-07-17"
                  payment_method_id: 1
                  payment_deadline_days: 8
                  bank_account_id: 3
                  invoice_block_id: 5
                  language_id: 1
                  currency_id: 1
                  price_mode: "net"
                  comment: "A(z) D-2026-00012 számú díjbekérő alapján."
                  items:
                    - { name: "Szolgáltatás", quantity: 2, unit_id: 2, unit_price: 10000, vat_rate_id: 1 }
              advance:
                summary: Előlegszámla (document_kind advance, bruttó árazás)
                value:
                  document_kind: "advance"
                  partner_name: "Minta Vevő Kft."
                  partner_country_id: 75
                  partner_postal_code: "1052"
                  partner_city: "Budapest"
                  partner_address: "Váci utca 10."
                  partner_tax_type: "has_tax_number"
                  partner_tax_number: "12345678-2-42"
                  partner_emails: ["penzugy@mintavevo.hu"]
                  fulfillment_date: "2026-07-17"
                  payment_method_id: 1
                  payment_deadline_days: 8
                  bank_account_id: 3
                  invoice_block_id: 5
                  language_id: 1
                  currency_id: 1
                  price_mode: "gross"
                  items:
                    - { name: "Előleg", quantity: 1, unit_id: 2, unit_price: 127000, vat_rate_id: 1 }
              final:
                summary: Végszámla (előleg beszámításával)
                description: |
                  A második tétel a beszámító sor: negatív mennyiség,
                  egységára az előlegszámla NETTÓ összege, és
                  `advance_invoice_id` mutat a kiállított előlegszámlára.
                value:
                  document_kind: "final"
                  partner_name: "Minta Vevő Kft."
                  partner_country_id: 75
                  partner_postal_code: "1052"
                  partner_city: "Budapest"
                  partner_address: "Váci utca 10."
                  partner_tax_type: "has_tax_number"
                  partner_tax_number: "12345678-2-42"
                  partner_emails: ["penzugy@mintavevo.hu"]
                  fulfillment_date: "2026-08-15"
                  payment_method_id: 1
                  payment_deadline_days: 8
                  bank_account_id: 3
                  invoice_block_id: 5
                  language_id: 1
                  currency_id: 1
                  price_mode: "net"
                  items:
                    - { name: "Fejlesztés", quantity: 1, unit_id: 2, unit_price: 500000, vat_rate_id: 1 }
                    - { name: "Beszámított előleg", quantity: -1, unit_id: 2, unit_price: 100000, vat_rate_id: 1, advance_invoice_id: 41 }
              modification:
                summary: Módosító számla (document_kind modification)
                description: |
                  A tételek a MÓDOSÍTÁS különbözetét tartalmazzák (jellemzően
                  az eredeti sorok negált előjellel + az új sorok).
                  Sztornó NEM így készül, hanem a `/invoices/{id}/storno`
                  végponttal.
                value:
                  document_kind: "modification"
                  original_invoice_id: 41
                  partner_name: "Minta Vevő Kft."
                  partner_country_id: 75
                  partner_postal_code: "1052"
                  partner_city: "Budapest"
                  partner_address: "Váci utca 10."
                  partner_tax_type: "has_tax_number"
                  partner_tax_number: "12345678-2-42"
                  partner_emails: ["penzugy@mintavevo.hu"]
                  fulfillment_date: "2026-07-17"
                  payment_method_id: 1
                  payment_deadline_days: 8
                  bank_account_id: 3
                  invoice_block_id: 5
                  language_id: 1
                  currency_id: 1
                  price_mode: "net"
                  items:
                    - { name: "Szolgáltatás", quantity: -2, unit_id: 2, unit_price: 10000, vat_rate_id: 1 }
      responses:
        "201":
          description: A létrehozott piszkozat (számolt összegekkel, tételekkel)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Számlák]
      summary: Számla lekérése (tételekkel)
      responses:
        "200":
          description: A számla
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Számlák]
      summary: Piszkozat módosítása
      description: Csak piszkozat módosítható — kiállított számlára `422` a válasz.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InvoiceInput" }
      responses:
        "200":
          description: A módosított piszkozat
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Számlák]
      summary: Piszkozat törlése
      description: Csak piszkozat törölhető — kiállított számlára `422` a válasz.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}/issue:
    post:
      tags: [Számlák]
      summary: Számla kiállítása
      description: |
        Sorszám (`PREFIX-ÉÉÉÉ-#####`), kelte és esedékesség rögzítése.
        Kiállítás után a számla nem módosítható és nem törölhető. Ha a
        vevő nem törzsbeli partner volt, automatikusan bekerül a
        partnertörzsbe. Éles módban (és NAV technikai userrel) elindul a
        NAV-adatszolgáltatás; **sandbox módban NAV-beküldés soha nincs**.
        Éles kulcsnál a kiállításhoz a vállalkozáshoz beállított NAV
        technikai felhasználó szükséges — enélkül a kérés 422-t ad
        (sandbox kulcsra a korlát nem vonatkozik).
        Opcionálisan ismétlődés is beállítható a kéréstörzsben.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                recurring_enabled: { type: boolean, description: "Ismétlődő számla bekapcsolása. Ha false (vagy hiányzik), a többi recurring_* mező figyelmen kívül marad." }
                recurring_interval_unit: { type: string, enum: [day, week, month, quarter, year] }
                recurring_interval_count: { type: integer, minimum: 1, maximum: 999, default: 1 }
                recurring_end_date: { type: string, format: date, description: "Vége-dátum (opcionális)" }
                recurring_max_runs: { type: integer, minimum: 1, maximum: 999, description: "Max. darabszám (opcionális)" }
      responses:
        "200":
          description: A kiállított számla (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}/storno:
    post:
      tags: [Számlák]
      summary: Sztornó számla készítése
      description: |
        Csak kiállított, még nem sztornózott számlára. A szerver az eredeti
        TELJES ellentétét (negált tételek) generálja **piszkozatként** — a
        sztornó tételei nem szerkeszthetők, kiállítása a normál
        `/invoices/{id}/issue` hívással történik.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string, maxLength: 255, description: "A sztornózás oka (a sztornó számlán megjelenik)" }
      responses:
        "201":
          description: A generált sztornó-piszkozat
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}/toggle-paid:
    post:
      tags: [Számlák]
      summary: Fizetve jelölés ki/be
      description: Csak kiállított számlán. A kintlévőség-számítás alapja.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A számla friss állapota (`paid_at` beállítva vagy törölve)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}/send:
    post:
      tags: [Számlák]
      summary: Számla küldése e-mailben
      description: |
        Csak kiállított számla küldhető. A levél a vevő összes e-mail
        címére megy, a PDF-et a vevő egyedi letöltő linkről éri el
        (a megnyitások naplózásra kerülnek). Sandbox módban a tárgy
        `[TESZT]` jelzést kap.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /invoices/{id}/pdf:
    get:
      tags: [Számlák]
      summary: Számla PDF
      description: Piszkozatnál „PISZKOZAT — NEM HITELES" jelöléssel; sandbox módban „TESZT BIZONYLAT" vízjellel.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A számla PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /invoices/{id}/deliveries:
    get:
      tags: [Számlák]
      summary: Számla kézbesítései
      description: A számla e-mail kiküldései (mikor, mely címekre) és a letöltő link megnyitásainak időpontjai.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /fee-requests:
    get:
      tags: [Díjbekérők]
      summary: Díjbekérők listája
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra vagy partner névre" }
        - { name: date_from, in: query, schema: { type: string, format: date } }
        - { name: date_to, in: query, schema: { type: string, format: date } }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott díjbekérő-lista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
    post:
      tags: [Díjbekérők]
      summary: Díjbekérő létrehozása
      description: |
        A díjbekérő azonnal sorszámot kap (`D-ÉÉÉÉ-#####`), nincs
        piszkozat-fázis, és később is bármikor módosítható. Opcionálisan
        ismétlődés is beállítható (`recurring_*` mezők, mint a számla
        kiállításánál).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FeeRequestInput" }
      responses:
        "201":
          description: A létrehozott díjbekérő (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /fee-requests/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Díjbekérők]
      summary: Díjbekérő lekérése (tételekkel)
      responses:
        "200":
          description: A díjbekérő
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Díjbekérők]
      summary: Díjbekérő módosítása
      description: Fizetettként jelölt (paid_at) díjbekérő nem módosítható — 422-t ad; előbb vond vissza a jelölést (toggle-paid).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FeeRequestInput" }
      responses:
        "200":
          description: A módosított díjbekérő
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Díjbekérők]
      summary: Díjbekérő törlése
      description: Fizetettként jelölt (paid_at) díjbekérő nem törölhető — 422-t ad.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /fee-requests/{id}/toggle-paid:
    post:
      tags: [Díjbekérők]
      summary: Fizetve jelölés ki/be
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A díjbekérő friss állapota
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
  /fee-requests/{id}/send:
    post:
      tags: [Díjbekérők]
      summary: Díjbekérő küldése e-mailben
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
  /fee-requests/{id}/pdf:
    get:
      tags: [Díjbekérők]
      summary: Díjbekérő PDF
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A díjbekérő PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /fee-requests/{id}/deliveries:
    get:
      tags: [Díjbekérők]
      summary: Díjbekérő kézbesítései
      description: A díjbekérő e-mail kiküldései és a letöltő link megnyitásainak időpontjai.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /quotations:
    get:
      tags: [Árajánlatok]
      summary: Árajánlatok listája
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra vagy partner névre" }
        - { name: date_from, in: query, schema: { type: string, format: date } }
        - { name: date_to, in: query, schema: { type: string, format: date } }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott árajánlat-lista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
    post:
      tags: [Árajánlatok]
      summary: Árajánlat létrehozása
      description: |
        Azonnal sorszámot kap (`A-ÉÉÉÉ-#####`). Az érvényesség
        (`valid_until`) a mentés napja + `validity_days` — minden
        módosításkor újraindul.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/QuotationInput" }
      responses:
        "201":
          description: A létrehozott árajánlat (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /quotations/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Árajánlatok]
      summary: Árajánlat lekérése (tételekkel)
      responses:
        "200":
          description: Az árajánlat
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Árajánlatok]
      summary: Árajánlat módosítása
      description: Elfogadottként jelölt (accepted_at) árajánlat nem módosítható — 422-t ad; előbb vond vissza a jelölést (toggle-accepted).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/QuotationInput" }
      responses:
        "200":
          description: A módosított árajánlat
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Árajánlatok]
      summary: Árajánlat törlése
      description: Elfogadottként jelölt (accepted_at) árajánlat nem törölhető — 422-t ad.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /quotations/{id}/toggle-accepted:
    post:
      tags: [Árajánlatok]
      summary: Elfogadva jelölés ki/be
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: Az árajánlat friss állapota
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
  /quotations/{id}/send:
    post:
      tags: [Árajánlatok]
      summary: Árajánlat küldése e-mailben
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
  /quotations/{id}/pdf:
    get:
      tags: [Árajánlatok]
      summary: Árajánlat PDF
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: Az árajánlat PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /quotations/{id}/deliveries:
    get:
      tags: [Árajánlatok]
      summary: Árajánlat kézbesítései
      description: Az árajánlat e-mail kiküldései és a letöltő link megnyitásainak időpontjai.
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /delivery-notes:
    get:
      tags: [Szállítólevelek]
      summary: Szállítólevelek listája
      description: "**Pro csomag.** Kisebb csomagnál a végpont 403-at ad."
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra vagy partner névre" }
        - { name: date_from, in: query, schema: { type: string, format: date } }
        - { name: date_to, in: query, schema: { type: string, format: date } }
        - { name: status, in: query, schema: { type: string, enum: [received, pending] }, description: "Átvéve / átvételre vár" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott szállítólevél-lista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
        "403": { description: "A szállítólevél a Pro csomagban érhető el" }
    post:
      tags: [Szállítólevelek]
      summary: Szállítólevél létrehozása
      description: |
        **Pro csomag.** Azonnal sorszámot kap; az előtag a vállalkozáson
        állítható (alapértelmezés: `SZL`), pl. `SZL-2026-00001`.
        A `show_prices` dönti el, hogy a PDF-en látszanak-e az árak
        (alapból **false**: csak megnevezés, mennyiség és egység).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DeliveryNoteInput" }
      responses:
        "201":
          description: A létrehozott szállítólevél (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /delivery-notes/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Szállítólevelek]
      summary: Szállítólevél lekérése (tételekkel)
      responses:
        "200":
          description: A szállítólevél
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Szállítólevelek]
      summary: Szállítólevél módosítása
      description: Átvettként jelölt (`received_at`) szállítólevél nem módosítható — 422-t ad; előbb vond vissza a jelölést (toggle-received).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DeliveryNoteInput" }
      responses:
        "200":
          description: A módosított szállítólevél
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Szállítólevelek]
      summary: Szállítólevél törlése
      description: Átvettként jelölt (`received_at`) szállítólevél nem törölhető — 422-t ad.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /delivery-notes/{id}/toggle-received:
    post:
      tags: [Szállítólevelek]
      summary: Átvéve jelölés ki/be
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A szállítólevél friss állapota
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
  /delivery-notes/{id}/send:
    post:
      tags: [Szállítólevelek]
      summary: Szállítólevél küldése e-mailben
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
  /delivery-notes/{id}/pdf:
    get:
      tags: [Szállítólevelek]
      summary: Szállítólevél PDF
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A szállítólevél PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /delivery-notes/{id}/deliveries:
    get:
      tags: [Szállítólevelek]
      summary: Szállítólevél kézbesítései
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /completion-certificates:
    get:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolások listája
      description: "**Pro csomag.** Kisebb csomagnál a végpont 403-at ad."
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra, partner névre vagy hivatkozásra" }
        - { name: date_from, in: query, schema: { type: string, format: date } }
        - { name: date_to, in: query, schema: { type: string, format: date } }
        - { name: status, in: query, schema: { type: string, enum: [accepted, pending] }, description: "Elfogadva / elfogadásra vár" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott lista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
        "403": { description: "A teljesítési igazolás a Pro csomagban érhető el" }
    post:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás létrehozása
      description: |
        **Pro csomag.** Azonnal sorszámot kap; az előtag a vállalkozáson
        állítható (alapértelmezés: `TIG`), pl. `TIG-2026-00001`.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CompletionCertificateInput" }
      responses:
        "201":
          description: A létrehozott teljesítési igazolás (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /completion-certificates/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás lekérése (tételekkel)
      responses:
        "200":
          description: A teljesítési igazolás
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás módosítása
      description: Elfogadottként jelölt (`accepted_at`) bizonylat nem módosítható — 422-t ad.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CompletionCertificateInput" }
      responses:
        "200":
          description: A módosított teljesítési igazolás
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás törlése
      description: Elfogadottként jelölt (`accepted_at`) bizonylat nem törölhető — 422-t ad.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /completion-certificates/{id}/toggle-accepted:
    post:
      tags: [Teljesítési igazolások]
      summary: Elfogadva jelölés ki/be
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A bizonylat friss állapota
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
  /completion-certificates/{id}/send:
    post:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás küldése e-mailben
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
  /completion-certificates/{id}/pdf:
    get:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás PDF
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A bizonylat PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /completion-certificates/{id}/deliveries:
    get:
      tags: [Teljesítési igazolások]
      summary: Teljesítési igazolás kézbesítései
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /work-sheets:
    get:
      tags: [Munkalapok]
      summary: Munkalapok listája
      description: "**Pro csomag.** Kisebb csomagnál a végpont 403-at ad."
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés sorszámra, partner névre vagy hivatkozásra" }
        - { name: date_from, in: query, schema: { type: string, format: date } }
        - { name: date_to, in: query, schema: { type: string, format: date } }
        - { name: status, in: query, schema: { type: string, enum: [received, pending] }, description: "Átvéve / átvételre vár" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott lista
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedDocuments" }
        "403": { description: "A munkalap a Pro csomagban érhető el" }
    post:
      tags: [Munkalapok]
      summary: Munkalap létrehozása
      description: |
        **Pro csomag.** Azonnal sorszámot kap; az előtag a vállalkozáson
        állítható (alapértelmezés: `ML`), pl. `ML-2026-00001`.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WorkSheetInput" }
      responses:
        "201":
          description: A létrehozott munkalap (sorszámmal)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /work-sheets/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [Munkalapok]
      summary: Munkalap lekérése (tételekkel)
      responses:
        "200":
          description: A munkalap
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Munkalapok]
      summary: Munkalap módosítása
      description: Átvettként jelölt (`received_at`) munkalap nem módosítható — 422-t ad.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WorkSheetInput" }
      responses:
        "200":
          description: A módosított munkalap
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
        "422": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Munkalapok]
      summary: Munkalap törlése
      description: Átvettként jelölt (`received_at`) munkalap nem törölhető — 422-t ad.
      responses:
        "200": { $ref: "#/components/responses/Deleted" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /work-sheets/{id}/toggle-received:
    post:
      tags: [Munkalapok]
      summary: Átvéve jelölés ki/be
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A bizonylat friss állapota
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Document" }
  /work-sheets/{id}/send:
    post:
      tags: [Munkalapok]
      summary: Munkalap küldése e-mailben
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "202": { $ref: "#/components/responses/Queued" }
  /work-sheets/{id}/pdf:
    get:
      tags: [Munkalapok]
      summary: Munkalap PDF
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200":
          description: A bizonylat PDF-je
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "404": { $ref: "#/components/responses/NotFound" }
  /work-sheets/{id}/deliveries:
    get:
      tags: [Munkalapok]
      summary: Munkalap kézbesítései
      parameters: [ { $ref: "#/components/parameters/id" } ]
      responses:
        "200": { $ref: "#/components/responses/DeliveryList" }
        "404": { $ref: "#/components/responses/NotFound" }

  /incoming-invoices:
    get:
      tags: [NAV-ból szinkronizált költség számlák]
      summary: NAV-ból szinkronizált költség számlák listája
      description: |
        A NAV Online Számla rendszeréből a vállalkozás részére kiállított
        számlák **automatikusan szinkronizált kivonata** (félóránként; a
        technikai felhasználónak „számlák lekérdezése" jogosultság is kell).
        **Csak olvasható** — a Szamlify nem szerkeszti; tételsorokat a NAV nem ad.

        **Standard csomagtól** érhető el. **Sandbox-kulccsal mindig üres
        lista** jön vissza (a költség számla valós NAV-adat, teszt-adatbázisban
        nincs) — a hívás és a válasz alakja így is tesztelhető.

        Inkrementális lekéréshez a `since` paraméter: a NAV-beérkezés
        (`nav_ins_date`) szerint adja az azóta érkezett sorokat — a legutóbbi
        válasz legnagyobb `nav_ins_date` értékét add át.
      parameters:
        - { name: search, in: query, schema: { type: string }, description: "Keresés szállító nevére/adószámára vagy sorszámra" }
        - { name: date_from, in: query, schema: { type: string, format: date }, description: "Kelte-tartomány kezdete" }
        - { name: date_to, in: query, schema: { type: string, format: date }, description: "Kelte-tartomány vége" }
        - { name: fulfillment_from, in: query, schema: { type: string, format: date }, description: "Teljesítés dátuma tartomány kezdete" }
        - { name: fulfillment_to, in: query, schema: { type: string, format: date }, description: "Teljesítés dátuma tartomány vége" }
        - { name: since, in: query, schema: { type: string, format: date-time }, description: "Csak az ennél KÉSŐBB a NAV-hoz beérkezett számlák (nav_ins_date, ISO 8601, időzónával)" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/perPage" }
      responses:
        "200":
          description: Lapozott lista (kelte szerint csökkenő)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaginatedIncomingInvoices" }
        "403": { description: "A NAV-ból szinkronizált költség számlák a Standard csomagtól érhetők el" }
        "422": { description: "Érvénytelen since paraméter" }
  /incoming-invoices/{id}:
    parameters: [ { $ref: "#/components/parameters/id" } ]
    get:
      tags: [NAV-ból szinkronizált költség számlák]
      summary: NAV-ból szinkronizált költség számla részletei
      description: "Sandbox-kulccsal 404 (nincs teszt-adat)."
      responses:
        "200":
          description: A költség számla kivonata
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IncomingInvoice" }
        "403": { description: "A NAV-ból szinkronizált költség számlák a Standard csomagtól érhetők el" }
        "404": { $ref: "#/components/responses/NotFound" }

  /countries:
    get:
      tags: [Törzsadatok]
      summary: Országok
      responses:
        "200":
          description: Országlista (Magyarország elöl)
          content:
            application/json:
              example: [ { id: 75, code: "HU", name: "Magyarország" } ]
  /currencies:
    get:
      tags: [Törzsadatok]
      summary: Pénznemek
      responses:
        "200":
          description: Pénznemek
          content:
            application/json:
              example: [ { id: 1, code: "HUF", name: "Magyar forint", symbol: "Ft" } ]
  /payment-methods:
    get:
      tags: [Törzsadatok]
      summary: Fizetési módok
      responses:
        "200":
          description: Fizetési módok
          content:
            application/json:
              example: [ { id: 1, code: "bank_transfer", name: "Banki átutalás" } ]
  /languages:
    get:
      tags: [Törzsadatok]
      summary: Bizonylat-nyelvek
      responses:
        "200":
          description: Nyelvek (hu, en)
          content:
            application/json:
              example: [ { id: 1, code: "hu", name: "Magyar" } ]
  /units:
    get:
      tags: [Törzsadatok]
      summary: Mennyiségi egységek
      responses:
        "200":
          description: Egységek
          content:
            application/json:
              example: [ { id: 1, code: "piece", name: "db" } ]
  /vat-rates:
    get:
      tags: [Törzsadatok]
      summary: ÁFA-kulcsok
      responses:
        "200":
          description: ÁFA-kulcsok (27/18/5/0 + AAM/TAM/FAD/EUFAD/EUK)
          content:
            application/json:
              example: [ { id: 1, code: "27", name: "27%", percent: 27 } ]
  /zip-codes:
    get:
      tags: [Törzsadatok]
      summary: Irányítószám ↔ település keresés
      parameters:
        - { name: zip, in: query, schema: { type: string }, description: "Irányítószám-előtag" }
        - { name: city, in: query, schema: { type: string }, description: "Település-előtag" }
      responses:
        "200":
          description: Találatok (max 20)
          content:
            application/json:
              example: [ { zip: "4400", city: "Nyíregyháza" } ]
  /invoice-blocks:
    get:
      tags: [Törzsadatok]
      summary: A vállalkozás bizonylattömbjei
      description: Bizonylat-készítéshez kell az `invoice_block_id` — az alapértelmezett tömb `is_default = true`.
      responses:
        "200":
          description: Bizonylattömbök
          content:
            application/json:
              example: [ { id: 5, name: "Belföldi számlák", prefix: "CLY-HU", is_default: true, bank_account_id: 3, sender_email: "info@ceg.hu", sender_name: "Cég Kft." } ]
  /bank-accounts:
    get:
      tags: [Törzsadatok]
      summary: A vállalkozás bankszámlái
      description: Banki átutalásos bizonylathoz kötelező a `bank_account_id`.
      responses:
        "200":
          description: Bankszámlák
          content:
            application/json:
              example: [ { id: 3, name: "Fő számla", bank_name: "OTP Bank", account_number: "11111111-22222222-33333333", iban: null, swift: null, currency_id: 1 } ]

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "API-kulcs: szmf_live_... vagy szmf_test_..."
  parameters:
    id:
      name: id
      in: path
      required: true
      schema: { type: integer }
    page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
      description: Oldalszám
    perPage:
      name: per_page
      in: query
      schema: { type: integer, minimum: 5, maximum: 100, default: 25 }
      description: Oldalméret
  responses:
    Unauthorized:
      description: Hiányzó vagy érvénytelen API-kulcs
      content:
        application/json:
          example: { message: "Hiányzó vagy érvénytelen API-kulcs." }
    NotFound:
      description: Nem létező vagy más vállalkozáshoz/módhoz tartozó erőforrás
      content:
        application/json:
          example: { message: "No query results." }
    ValidationError:
      description: Validációs vagy állapot-hiba (magyar üzenetekkel)
      content:
        application/json:
          example:
            message: "Az adószám formátuma: 12345678-1-12."
            errors: { partner_tax_number: ["Az adószám formátuma: 12345678-1-12."] }
    Deleted:
      description: Sikeres törlés
      content:
        application/json:
          example: { deleted: true }
    Queued:
      description: A küldés ütemezve (queue)
      content:
        application/json:
          example: { queued: true }
    DeliveryList:
      description: A bizonylat kiküldései és a letöltő link megnyitásai
      content:
        application/json:
          schema:
            type: object
            properties:
              deliveries:
                type: array
                items: { $ref: "#/components/schemas/Delivery" }
          example:
            deliveries:
              - id: 12
                sent_at: "2026-07-20T22:36:58+02:00"
                pending: false
                emails: ["vevo@example.com"]
                opens: ["2026-07-20T22:37:37+02:00", "2026-07-21T08:12:03+02:00"]
  schemas:
    Partner:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        country_id: { type: integer }
        postal_code: { type: string }
        city: { type: string }
        address: { type: string }
        tax_type: { type: string, enum: [has_tax_number, no_tax_number, foreign] }
        tax_number: { type: [string, "null"], description: "Belföldi ÁFA alanynál kötelező, 12345678-1-12 formátum" }
        eu_tax_number: { type: [string, "null"] }
        emails: { type: array, items: { type: string, format: email } }
        note: { type: [string, "null"] }
        default_payment_deadline_days: { type: integer }
        country:
          type: object
          properties: { id: { type: integer }, code: { type: string }, name: { type: string } }
    PartnerInput:
      type: object
      required: [name, country_id, postal_code, city, address, tax_type, emails, default_payment_deadline_days]
      properties:
        name: { type: string, maxLength: 255 }
        country_id: { type: integer, description: "Lásd GET /countries" }
        postal_code: { type: string, maxLength: 10 }
        city: { type: string }
        address: { type: string }
        tax_type:
          type: string
          enum: [has_tax_number, no_tax_number, foreign]
          description: "has_tax_number = belföldi ÁFA alany, no_tax_number = természetes személy / adószám nélkül, foreign = külföldi"
        tax_number: { type: string, description: "Belföldi ÁFA alanynál kötelező, formátum: 12345678-1-12" }
        eu_tax_number: { type: string, description: "Pl. HU12345678" }
        emails: { type: array, minItems: 1, maxItems: 10, items: { type: string, format: email } }
        note: { type: string, maxLength: 2000, description: "Belső megjegyzés" }
        default_payment_deadline_days: { type: integer, minimum: 1, maximum: 60 }
      example:
        name: "Minta Vevő Kft."
        country_id: 75
        postal_code: "1052"
        city: "Budapest"
        address: "Váci utca 10."
        tax_type: "has_tax_number"
        tax_number: "12345678-2-42"
        emails: ["penzugy@mintavevo.hu"]
        default_payment_deadline_days: 8
    Product:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        unit_id: { type: integer }
        net_unit_price: { type: number }
        vat_rate_id: { type: integer }
        unit:
          type: object
          properties: { id: { type: integer }, code: { type: string }, name: { type: string } }
        vat_rate:
          type: object
          properties: { id: { type: integer }, code: { type: string }, name: { type: string }, percent: { type: [number, "null"] } }
    ProductInput:
      type: object
      required: [name, unit_id, net_unit_price, vat_rate_id]
      properties:
        name: { type: string, maxLength: 255, description: "Vállalkozáson belül egyedi" }
        unit_id: { type: integer, description: "Lásd GET /units" }
        net_unit_price: { type: number, minimum: 0, description: "Nettó egységár (max 2 tizedes)" }
        vat_rate_id: { type: integer, description: "Lásd GET /vat-rates" }
      example: { name: "Konzultáció", unit_id: 2, net_unit_price: 25000, vat_rate_id: 1 }
    DocumentItemInput:
      type: object
      required: [name, quantity, unit_id, unit_price, vat_rate_id]
      properties:
        name: { type: string, maxLength: 255 }
        comment: { type: string, maxLength: 500, description: "Tétel-megjegyzés — a bizonylaton a tételnév alatt, kisebb betűvel jelenik meg (pl. domain név, időszak). A terméktörzsbe NEM kerül be." }
        quantity: { type: number, description: "Max 3 tizedes; negatív is lehet (nulla nem) — normál számlán beszámítás/jóváírás sorhoz, de a számla végösszege nem mehet negatívba" }
        unit_id: { type: integer }
        unit_price: { type: number, description: "A price_mode szerint nettó VAGY bruttó egységár (max 2 tizedes)" }
        vat_rate_id: { type: integer }
        advance_invoice_id: { type: integer, description: "CSAK számlán (document_kind: final): az előleg-beszámító sor a saját cég KIÁLLÍTOTT előlegszámlájára (advance) hivatkozik — a sor mennyisége negatív, egységára az előleg nettója. Más bizonylattípuson nem értelmezett." }
    DocumentInputBase:
      type: object
      required: [partner_name, partner_country_id, partner_postal_code, partner_city, partner_address, partner_tax_type, partner_emails, invoice_block_id, language_id, currency_id, price_mode]
      properties:
        partner_id: { type: integer, description: "Törzsbeli partner id-ja (opcionális; a snapshot mezőket akkor is küldeni kell)" }
        partner_name: { type: string }
        partner_country_id: { type: integer }
        partner_postal_code: { type: string }
        partner_city: { type: string }
        partner_address: { type: string }
        partner_tax_type: { type: string, enum: [has_tax_number, no_tax_number, foreign] }
        partner_tax_number: { type: string, description: "Belföldi ÁFA alanynál kötelező" }
        partner_emails: { type: array, minItems: 1, maxItems: 10, items: { type: string, format: email } }
        invoice_block_id: { type: integer, description: "Lásd GET /invoice-blocks" }
        language_id: { type: integer, description: "Lásd GET /languages" }
        currency_id: { type: integer, description: "Lásd GET /currencies" }
        price_mode: { type: string, enum: [net, gross], description: "A tétel-egységárak értelmezése" }
        comment: { type: string, maxLength: 5000 }
        intermediated_service: { type: boolean, default: false, description: "Csak számlán: „A számla közvetített szolgáltatást tartalmaz.” záradék + NAV intermediatedService" }
        no_financial_settlement: { type: boolean, default: false, description: "Csak számlán: „A számla pénzügyi teljesítést nem igényel.” záradék" }
        items: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/DocumentItemInput" } }
    InvoiceInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          required: [fulfillment_date, payment_method_id, payment_deadline_days]
          properties:
            document_kind: { type: string, enum: [normal, modification, advance, final], description: "Módosító számlához: modification + original_invoice_id (sztornó CSAK a /storno végponttal készül). Előlegszámla: advance (az előleg BRUTTÓ — price_mode: gross ajánlott). Végszámla: final — a tételek között áfa-kulcsonként egy NEGATÍV, advance_invoice_id-vel jelölt előleg-beszámító sorral; egy előlegszámla csak egyszer számolható be (kiállításkor 422)." }
            original_invoice_id: { type: integer, description: "Módosító számlánál kötelező" }
            source_type: { type: string, enum: [fee_request, quotation, delivery_note, completion_certificate, work_sheet], description: "Ha másik bizonylatból készül (díjbekérő, árajánlat, szállítólevél, teljesítési igazolás, munkalap) — kiállításkor a forrás „Számlázva\" jelölést kap, és a számla PDF-jén megjelenik a hivatkozás" }
            source_id: { type: integer }
            fulfillment_date: { type: string, format: date }
            continuous_fulfillment: { type: boolean, default: false }
            payment_method_id: { type: integer, description: "Lásd GET /payment-methods" }
            payment_deadline_days: { type: integer, minimum: 0, maximum: 90 }
            bank_account_id: { type: integer, description: "Banki átutalásnál KÖTELEZŐ (lásd GET /bank-accounts)" }
      example:
        partner_name: "Minta Vevő Kft."
        partner_country_id: 75
        partner_postal_code: "1052"
        partner_city: "Budapest"
        partner_address: "Váci utca 10."
        partner_tax_type: "has_tax_number"
        partner_tax_number: "12345678-2-42"
        partner_emails: ["penzugy@mintavevo.hu"]
        fulfillment_date: "2026-07-17"
        payment_method_id: 1
        payment_deadline_days: 8
        bank_account_id: 3
        invoice_block_id: 5
        language_id: 1
        currency_id: 1
        price_mode: "net"
        items:
          - { name: "Szolgáltatás", quantity: 2, unit_id: 2, unit_price: 10000, vat_rate_id: 1 }
    FeeRequestInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          required: [payment_method_id, payment_deadline_days]
          properties:
            payment_method_id: { type: integer }
            payment_deadline_days: { type: integer, minimum: 0, maximum: 90 }
            bank_account_id: { type: integer, description: "Banki átutalásnál KÖTELEZŐ" }
            recurring_enabled: { type: boolean, description: "Ismétlődő díjbekérő" }
            recurring_interval_unit: { type: string, enum: [day, week, month, quarter, year] }
            recurring_interval_count: { type: integer, minimum: 1, maximum: 999 }
            recurring_end_date: { type: string, format: date }
            recurring_max_runs: { type: integer, minimum: 1, maximum: 999 }
    QuotationInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          required: [validity_days]
          properties:
            validity_days: { type: integer, minimum: 1, maximum: 365, default: 30, description: "Érvényesség napokban a mentéstől" }
    DeliveryNoteInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          properties:
            delivery_date: { type: [string, "null"], format: date, description: "Szállítás/átadás napja" }
            delivery_address: { type: [string, "null"], maxLength: 255, description: "Szállítási cím, ha eltér a vevőétől" }
            show_prices: { type: boolean, default: false, description: "Látszanak-e az árak a PDF-en" }
    WorkSheetInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          properties:
            reference_number: { type: [string, "null"], maxLength: 100, description: "Kár- vagy ügyszám" }
            partner_phone: { type: [string, "null"], maxLength: 40, description: "Ügyfél telefonszáma (a partnertörzsből töltődik)" }
            work_location: { type: [string, "null"], maxLength: 255, description: "Munkavégzés helyszíne, ha eltér az ügyfél címétől" }
            reported_issue: { type: [string, "null"], maxLength: 5000, description: "Bejelentett hiba / munka" }
            work_done: { type: [string, "null"], maxLength: 5000, description: "Elvégzett munka, felhasznált anyagok" }
            arrived_at: { type: [string, "null"], format: date-time, description: "Helyszínre érkezés" }
            show_prices: { type: boolean, default: true, description: "Látszanak-e az árak a PDF-en" }
    CompletionCertificateInput:
      allOf:
        - $ref: "#/components/schemas/DocumentInputBase"
        - type: object
          properties:
            reference_number: { type: [string, "null"], maxLength: 100, description: "Szerződés-/megrendelésszám" }
            performance_from: { type: [string, "null"], format: date }
            performance_to: { type: [string, "null"], format: date, description: "Nem lehet korábbi a kezdetnél" }
            source_type: { type: [string, "null"], enum: [quotation, null], description: "Árajánlatból konvertálva" }
            source_id: { type: [integer, "null"] }
    IncomingInvoice:
      type: object
      description: "A NAV-ból szinkronizált költség számla kivonata — tételsorok nélkül (a NAV nem adja)."
      properties:
        id: { type: integer }
        supplier_name: { type: [string, "null"], description: "A szállító neve (a NAV adata)" }
        supplier_tax_number: { type: string, description: "A szállító adószámának első 8 jegye" }
        invoice_number: { type: string, description: "A szállító számlasorszáma" }
        invoice_operation: { type: string, enum: [CREATE, MODIFY, STORNO], description: "NAV-művelet: eredeti / módosító / sztornó" }
        invoice_category: { type: [string, "null"], enum: [NORMAL, SIMPLIFIED, AGGREGATE, null], description: "NAV számlakategória" }
        original_invoice_number: { type: [string, "null"], description: "Módosító/sztornó számlánál az eredeti sorszám" }
        issue_date: { type: string, format: date, description: "Kelte" }
        fulfillment_date: { type: [string, "null"], format: date, description: "Teljesítés dátuma" }
        payment_date: { type: [string, "null"], format: date, description: "Fizetési határidő" }
        payment_method: { type: [string, "null"], enum: [TRANSFER, CASH, CARD, VOUCHER, OTHER, null] }
        currency: { type: string, example: HUF }
        net_amount: { type: number, description: "Nettó, a számla pénznemében" }
        vat_amount: { type: number, description: "ÁFA, a számla pénznemében" }
        gross_amount: { type: number, description: "Bruttó (nettó + ÁFA), a számla pénznemében" }
        net_amount_huf: { type: number, description: "Nettó forintban (a NAV-adatszolgáltatás árfolyamával)" }
        vat_amount_huf: { type: number, description: "ÁFA forintban" }
        gross_amount_huf: { type: number, description: "Bruttó forintban" }
        nav_ins_date: { type: string, format: date-time, description: "A NAV-hoz beérkezés időpontja (UTC) — a `since` szűrő kurzora" }
    PaginatedIncomingInvoices:
      allOf:
        - $ref: "#/components/schemas/Pagination"
        - type: object
          properties:
            data: { type: array, items: { $ref: "#/components/schemas/IncomingInvoice" } }
    Document:
      type: object
      description: |
        Bizonylat (számla / díjbekérő / árajánlat) — a mezőkészlet típusonként
        kissé eltér. Fontosabb mezők: sorszám (`invoice_number` /
        `document_number`), státusz (számlánál `draft`/`issued`), a szerver
        által számolt `net_total`/`vat_total`/`gross_total` +
        `rounding_difference`, a vevő-snapshot (`partner_*`), a bank-snapshot
        (`bank_*_snapshot`), `paid_at`/`accepted_at`, `last_sent_at`, és az
        `items` tömb a számolt tétel-összegekkel.
      additionalProperties: true
      properties:
        id: { type: integer }
        invoice_number: { type: [string, "null"], description: "Számlánál (kiállítás után)" }
        document_number: { type: string, description: "Díjbekérőnél/árajánlatnál" }
        status: { type: string, enum: [draft, issued], description: "Csak számlánál" }
        net_total: { type: [number, string] }
        vat_total: { type: [number, string] }
        gross_total: { type: [number, string] }
        rounding_difference: { type: [number, string] }
        paid_at: { type: [string, "null"], format: date-time }
        last_sent_at: { type: [string, "null"], format: date-time }
        items: { type: array, items: { type: object, additionalProperties: true } }
    Delivery:
      type: object
      description: Egy e-mail kiküldés és a hozzá tartozó letöltések.
      properties:
        id: { type: integer }
        sent_at: { type: string, format: date-time, description: "A kiküldés időpontja" }
        pending: { type: boolean, description: "true, amíg a levél a küldési sorban van" }
        emails: { type: array, items: { type: string, format: email }, description: "A kiküldés címzettjei" }
        opens: { type: array, items: { type: string, format: date-time }, description: "A letöltő link megnyitásainak időpontjai" }
    Pagination:
      type: object
      properties:
        current_page: { type: integer }
        last_page: { type: integer }
        per_page: { type: integer }
        total: { type: integer }
    PaginatedPartners:
      allOf:
        - $ref: "#/components/schemas/Pagination"
        - type: object
          properties:
            data: { type: array, items: { $ref: "#/components/schemas/Partner" } }
    PaginatedProducts:
      allOf:
        - $ref: "#/components/schemas/Pagination"
        - type: object
          properties:
            data: { type: array, items: { $ref: "#/components/schemas/Product" } }
    PaginatedDocuments:
      allOf:
        - $ref: "#/components/schemas/Pagination"
        - type: object
          properties:
            data: { type: array, items: { $ref: "#/components/schemas/Document" } }
