# RezoSoft API 1.8.0: full reference > Generated from https://api.rezosoft.ro/docs/openapi.json (OpenAPI 3.1.0). Do not edit by hand. Base URL: `https://api.rezosoft.ro/v2/external`. Every request needs the header `x-api-key`. ## Introduction > ⚠️ **Activare modul**: Trebuie să activați acest modul de la **Configurare → Abonament RezoSoft** și apoi apăsați pe butonul "Schimbă abonamentul", și bifați și modulul de "Acces API". ### Primii pași 1. Activați modulul **Acces API** (vezi mai sus). 2. Generați o cheie din **Setări → Integrări → Chei API**. Cheia se afișează o singură dată; păstrați-o pe server, niciodată în aplicații mobile sau în browser. 3. Verificați conexiunea: ```bash curl https://api.rezosoft.ro/v2/external/connection -H "x-api-key: CHEIA_DVS" ``` 4. Citiți ID-urile de care aveți nevoie: `GET /document-series/invoice` (seria), `GET /document-templates` (template-ul PDF), `GET /inventories` (gestiunea, dacă scădeți stoc). 5. Emiteți o factură cu `POST /documents` și folosiți `pdf_url` din răspuns (sau `GET /documents/invoice/{id}/download-pdf`). ```bash curl -X POST https://api.rezosoft.ro/v2/external/documents \ -H "x-api-key: CHEIA_DVS" -H "Content-Type: application/json" \ -d '{"document_type":"invoice","document_series_id":4,"document_template_id":1, "external_id":"COMANDA-1001","idempotent":true, "client":{"name":"Client Test SRL","fiscal_code":"RO12345678","country_code":"RO"}, "products":[{"name":"Serviciu","quantity":1,"vat_percentage":21,"price_with_vat":121}]}' ``` Fiecare endpoint are exemple de cod generate automat (cURL, PHP, Node.js, Python și altele) în panoul din dreapta, de unde se poate și testa direct cu cheia dvs. Specificația OpenAPI (butonul **Descarcă specificația OpenAPI**) se poate importa în Postman / Insomnia sau într-un generator de clienți. **Pentru agenți AI / LLM:** [llms.txt](https://api.rezosoft.ro/llms.txt) (rezumat și reguli de implementare) și [llms-full.txt](https://api.rezosoft.ro/llms-full.txt) (toată documentația într-un singur fișier Markdown), generate din aceeași specificație. ### Autentificare Toate endpoint-urile necesită header-ul `x-api-key` cu o cheie API validă generată din **Setări → Integrări → Chei API**. O cheie lipsă sau greșită răspunde 401. ### Limită de cereri Maximum **150 de cereri pe minut per adresă IP**, numărate pe toate endpoint-urile. Fiecare răspuns poartă `X-RateLimit-Limit` și `X-RateLimit-Remaining`. Peste limită răspunsul este **429** cu `Retry-After` (secunde) și `X-RateLimit-Reset`; așteptați cât indică `Retry-After` înainte de reîncercare. Pentru volume mari folosiți sincronizarea incrementală (mai jos) în locul citirii repetate a acelorași date. ### Erori | Cod | Când | Corp | |-----|------|------| | 400 | Parametri invalizi (tip de document necunoscut, interval / cursor invalid) | `{"status": "error", "message": "..."}` | | 401 | Cheie API lipsă sau invalidă | `{"status": "error", "message": "Unauthorized"}` | | 403 | Utilizatorul cheii nu mai are acces la companie | `{"status": "error", "message": "..."}` | | 404 | Resursa nu există sau aparține altei companii | `{"status": "error", "message": "..."}` (`code: "not_found"` pe documente) | | 409 | Conflict: cerere cu același `external_id` deja în curs (`in_progress`, se reîncearcă identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`) | `{"status": "error", "code": "...", "message": "..."}` | | 422 | Validare: `errors` are mesajele pe câmp; sau o regulă de business (`amount_too_high`, `currency_mismatch`, `generate_failed`, `no_bnr_rate`) | `{"message": "...", "errors": {"camp": ["..."]}}` sau `{"status": "error", "code": "...", "message": "..."}` | | 429 | Limita de cereri depășită | `{"message": "Too Many Attempts."}` | | 5xx | Eroare internă; se poate reîncerca, de preferat cu `external_id` + `idempotent` | — | Tratați `code` (când există) ca identificator stabil; `message` poate fi reformulat. ### Reîncercări sigure (idempotență) `POST /documents` cu `external_id` și `"idempotent": true` emite documentul o singură dată: aceeași cerere trimisă din nou întoarce același `id`, `number` și `key`, cu `"existing": true`. Plățile pe factură (`POST /documents/invoice/{id}/payments`) și facturarea unei proforme (`POST /documents/proform/{id}/invoice`) sunt idempotente în același fel. Un 409 `in_progress` (o cerere cu același `external_id` încă rulează; a doua așteaptă până la 10 secunde înainte de a răspunde așa) sau o eroare de rețea se reîncearcă după câteva secunde cu exact aceeași cerere. ### Facturi și e-Factura Facturile emise prin API se comportă ca cele emise din aplicație: dacă firma are activă trimiterea automată în e-Factura (Setări → e-Factura), factura se trimite la ANAF automat după întârzierea configurată (1–4 zile de la data facturii). Facturile către clienți din afara României nu se trimit. Data documentului este întotdeauna ziua curentă (ora României); nu se poate trimite altă dată. Pentru o comandă deja plătită trimiteți încasarea direct în `POST /documents` (`payment`), iar cu `update_stock` liniile legate de produse scad stocul (poate deveni negativ), cele fără produs găsit nu. ### Sincronizare incrementală Se aplică endpoint-urilor `reports/pos/receipts`, `reports/pos/orders`, `reports/pos/cancelled-items`, `reports/pos/incomes`, `reports/pos/products`, `reports/pos/z` și `reports/stock/movements` (`updated_since` și pe `products`, `reports/pos/tables`, `recipes`). | Parametru | Descriere | |-----------|-----------| | `from`, `to` | Interval inclusiv, ora României (`Y-m-d H:i:s` sau ISO 8601). O dată fără oră înseamnă ziua întreagă (`to=2026-10-06` = până la 23:59:59). Au prioritate față de `period_from` / `period_to`. | | `day_start` | Doar `incomes` și `z`: ora la care începe ziua de activitate. `05:00` se aliniază cu datarea raportului Z. | | `after_id` | Cursor: rândurile cu `id > after_id`, crescător, `per_page` (max. 500) pe apel. Continuați cu `pagination.next_after_id` până devine `null`. | | `updated_since` | Rândurile modificate de la moment, inclusiv ștergerile (cu `deleted_at`). | Fără interval explicit se folosește ziua de azi; cu `after_id` sau `updated_since` și fără `from`/`to`, nu se aplică niciun interval. **Rețeta recomandată:** 1. Import inițial: parcurgeți tot istoricul cu `after_id` (pornind de la 0) până când `next_after_id` este `null`. Rețineți `server_time` din **primul** răspuns. 2. Apoi, periodic: `updated_since=`, parcurs cu `after_id` / paginare; rețineți `server_time` din primul răspuns al rundei pentru runda următoare. 3. Folosiți întotdeauna `server_time` (ceasul serverului), nu ceasul propriu: diferențele de oră ar pierde rânduri. 4. Rândurile pot reapărea între runde; faceți upsert după `id`. **Limitări cunoscute:** starea de bucătărie este pe comandă, nu pe linie; modificările de cantitate și transferurile de masă nu sunt înregistrate de POS; închiderea de casă este pe terminal (raport Z), nu pe operator; liniile unei comenzi de bucătărie rămân disponibile 90 de zile după închiderea mesei; schimbarea ospătarului unei mese nu apare în `reports/pos/tables?updated_since` (citiți snapshot-ul complet); vânzările POS nu apar în mișcările de stoc (se citesc din `reports/pos/receipts`). ### Versiuni Versiunea din antet urmează [SemVer](https://semver.org/lang/ro/): **major** = o schimbare care poate strica o integrare existentă (câmp sau endpoint eliminat ori redenumit, autentificare schimbată), **minor** = endpoint, parametru sau câmp nou, **patch** = doar corecturi ale documentației. Câmpurile noi pot apărea oricând în răspunsuri, deci ignorați câmpurile pe care nu le cunoașteți. Înainte de a fi eliminat, un câmp sau endpoint este marcat `deprecated` în această documentație și anunțat mai jos. ### Istoric versiuni - **1.8.0** (2026-10-16) — Cod intern pe produse: câmpul `internal_code` în lista și detaliile produselor, acceptat la `POST /products`; căutarea din lista produselor îl include. - **1.7.0** (2026-10-07) — Evenimente webhook pentru produse: `product.created`, `product.updated`, `product.deleted`, `product.stock_changed`. Webhook-ul vechi `product-update` este marcat depreciat. Webhook-urile abonate la toate evenimentele (`*`) primesc și evenimentele de produs. - **1.6.0** (2026-10-07) — Webhook-uri semnate pentru POS: bonuri, comenzi de bucătărie, mese (`pos.*`), administrare prin `/webhooks` (creare, test, rotire secret, jurnal de livrări, retrimitere); o cheie restricționată vede și administrează doar webhook-urile limitate la gestiunile ei. - **1.5.2** (2026-10-07) — Documentat: trimiterea automată în e-Factura, stocul la emitere (linii fără produs, stoc negativ), încasarea la emitere pentru comenzi plătite, data documentului (azi) și durata unui 409 `in_progress`. - **1.5.1** (2026-10-07) — Versiuni pentru agenți AI: `llms.txt` și `llms-full.txt`, generate din specificație. Corectat: `vat_percentage` acceptă orice cotă a companiei (lista veche 19/9/5/0 era greșită); documentat comportamentul exact al reîncercării idempotente. - **1.5.0** (2026-10-07) — Documentate: detaliu document, modificare / anulare / facturare proformă, încasări pe factură. `POST /documents`: documentate `currency` și `idempotent`; `use_product_name_from_db` are implicit `true`. Documentate răspunsurile 409, 422 și 429; secțiuni noi: primii pași, limită de cereri, erori, idempotență, sincronizare incrementală. Documentația are o interfață nouă, cu exemple de cod generate pentru fiecare endpoint și testare directă; specificația se poate descărca din `openapi.json`. - **1.4.0** (2026-10-06) — POS: mese deschise (`reports/pos/tables`), istoric comenzi de bucătărie, linii anulate, câmpuri noi pe bonuri; mișcări de stoc, rețete; parametrii `from`, `to`, `day_start`, `after_id`, `updated_since`. - **1.3.0** (2026-08-27 – 09-02) — Creare produs și NIR, listare documente, e-Facturi primite. - **1.2.0** (2026-05-07) — Raport Z POS. - **1.1.0** (2026-04-27 – 04-29) — Note de recepție (listare, detaliu, PDF), rapoarte POS, plată la emiterea facturii. Câmpuri redenumite (ex. `vat_percent` → `vat_percentage` pe liniile bonurilor). - **1.0.0** (2026-02) — Conexiune, template-uri, serii, gestiuni, produse, emitere document, PDF, email, nomenclator județe / localități. Versiunile anterioare 1.5.0 au fost atribuite retroactiv, după istoricul modificărilor. ## Endpoints ### Conexiune Test conexiune API #### `GET /connection`: Test conexiune operationId: `testConnection` Verifică validitatea cheii API Responses: - **200** Conexiune reușită → object - `status` (string) - `message` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/connection" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Resurse Obținere resurse (template-uri, serii, gestiuni) #### `GET /document-templates`: Lista template-uri documente operationId: `getDocumentTemplates` Returnează lista template-urilor de factură disponibile pentru companie Responses: - **200** Lista template-uri → object - `status` (string) - `templates` (`DocumentTemplate`[]) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/document-templates" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /document-series/{documentType}`: Lista serii documente operationId: `getDocumentSeries` Returnează seriile de documente pentru un anumit tip Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentType` | path | string | yes | Tipul documentului; one of `"invoice"`, `"proform"` | Responses: - **200** Lista serii → object - `status` (string) - `series` (`DocumentSeries`[]) - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/document-series/{documentType}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /inventories`: Lista gestiuni operationId: `getInventories` Returnează gestiunile la care utilizatorul asociat cheii API are acces Responses: - **200** Lista gestiuni → object - `status` (string) - `inventories` (`Inventory`[]) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/inventories" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Produse Interogare produse și stocuri #### `POST /products`: Creare produs operationId: `createProduct` Creează un produs într-o gestiune. Câmpul `code` este folosit și drept cod de bare — dacă nu este trimis, este generat automat; trebuie să fie unic în gestiune. Prețul de vânzare poate fi trimis cu sau fără TVA, celălalt fiind calculat pe baza `vat_percent`. Produsul este sincronizat automat cu POS-urile companiei, la fel ca la crearea din aplicație. Request body (JSON, required): Type: `CreateProductRequest` Example: ```json { "inventory_id": 12, "name": "Produs Exemplu", "code": "5941234567890", "vat_percent": 21, "price_with_vat": 12.5, "unit": "buc", "type": "goods" } ``` Responses: - **201** Produsul a fost creat → object - `status` (string) - `message` (string) - `product` (`ProductInfo`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **422** Date invalide (ex: cod de produs deja existent în gestiune, cotă TVA inexistentă) → object - `status` (string) - `message` (string) - `errors` (object): Erori de validare per câmp (opțional) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/products" \ -H "x-api-key: $REZOSOFT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"inventory_id":12,"name":"Produs Exemplu","code":"5941234567890","vat_percent":21,"price_with_vat":12.5,"unit":"buc","type":"goods"}' ``` #### `GET /products`: Lista produse operationId: `getProducts` Returnează lista produselor dintr-o gestiune cu paginare și căutare opțională Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `category_id` | query | integer | no | Doar produsele din categoria dată. | | `department_id` | query | integer | no | Doar produsele din departamentul dat. | | `inventory_id` | query | integer | yes | ID-ul gestiunii | | `with_stock` | query | string | no | Trimite `true` pentru a include stocul curent; one of `"true"` | | `search` | query | string | no | Caută în numele, codul sau codul intern al produsului | | `page` | query | integer | no | Numărul paginii | | `per_page` | query | integer | no | Produse per pagină (maxim 1000) | Responses: - **200** Lista produse → object - `status` (string) - `products` (`ProductInfo`[]) - `pagination` (`Pagination`) - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/products" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /products/{value}`: Detalii produs operationId: `getProduct` Returnează informații despre un singur produs, căutat după ID sau cod (barcode) Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `value` | path | string | yes | Valoarea de căutare (ID-ul sau codul produsului) | | `field` | query | string | no | Câmpul de căutare; one of `"id"`, `"code"` | | `with_stock` | query | string | no | Trimite `true` pentru a include stocul curent; one of `"true"` | Responses: - **200** Informații produs → object - `status` (string) - `product` (`ProductInfo`) - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/products/{value}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Documente Creare și gestionare documente #### `POST /documents`: Creare document operationId: `createDocument` Creează o factură sau o proformă Request body (JSON, required): Type: `CreateDocumentRequest` Example: ```json { "document_type": "invoice", "document_series_id": 4, "document_template_id": 1, "client": { "name": "Test Client SRL", "fiscal_code": "RO12345678", "email": "client@example.com", "phone": "+40700000000", "address": "Strada Exemplu 123", "county_code": "TM", "city_text": "Timișoara", "country_code": "RO" }, "products": [ { "name": "Serviciu consultanță", "quantity": 2, "vat_percentage": "21", "price_with_vat": 121 } ], "save_client": false, "update_stock": false, "send_email_after_issue": false } ``` Responses: - **200** Document creat cu succes → `CreateDocumentResponse` - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **409** (`Conflict`) `in_progress` (aceeași cerere este deja în curs sau conflict de concurență; reîncercați identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`). → `CodedError` - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/documents" \ -H "x-api-key: $REZOSOFT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"document_type":"invoice","document_series_id":4,"document_template_id":1,"client":{"name":"Test Client SRL","fiscal_code":"RO12345678","email":"client@example.com","phone":"+40700000000","address":"Strada Exemplu 123","county_code":"TM","city_text":"Timișoara","country_code":"RO"},"products":[{"name":"Serviciu consultanță","quantity":2,"vat_percentage":"21","price_with_vat":121}],"save_client":false,"update_stock":false,"send_email_after_issue":false}' ``` #### `GET /documents/{documentType}`: Listare documente emise operationId: `listDocuments` Returnează lista paginată a facturilor sau proformelor emise, cu datele clientului și totalurile. Ordonare: date_at descrescător, apoi id descrescător. Documentele șterse nu sunt incluse. Pentru PDF folosiți endpoint-ul de descărcare. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentType` | path | string | yes | Tipul documentului; one of `"invoice"`, `"proform"` | | `period_from` | query | string (date) | no | Filtrează după date_at >= (YYYY-MM-DD). | | `period_to` | query | string (date) | no | Filtrează după date_at <= (YYYY-MM-DD). | | `client_fiscal_code` | query | string | no | Codul fiscal al clientului (potrivire exactă, insensibilă la majuscule). | | `search` | query | string | no | Caută în numele clientului sau în numărul documentului (serie + număr). | | `payment_status` | query | integer | no | Doar pentru facturi: starea plății, aceeași valoare numerică folosită în aplicație. | | `efactura_status` | query | string | no | Doar pentru facturi: starea trimiterii în e-Factura.; one of `"not_sent"`, `"processing"`, `"success"`, `"error"`, `"not_company"`, `"not_eu_company"`, `"not_ro_company"`, `"not_ro_person"` | | `page` | query | integer | no | Pagina. | | `per_page` | query | integer | no | Documente per pagină (max 100). | Responses: - **200** Lista documentelor → `DocumentListResponse` - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/documents/{documentType}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /documents/{documentType}/{documentId}`: Detalii document operationId: `showDocument` Antetul, liniile și legăturile unei proforme sau facturi. Pentru factură include starea plății și încasările. Un `documentType` greșit sau un id nenumeric răspunde cu un 404 fără corp JSON. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentType` | path | string | yes | one of `"proform"`, `"invoice"` | | `documentId` | path | integer | yes | ID-ul documentului | Responses: - **200** Documentul → `DocumentDetail` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`DocumentNotFound`) Documentul nu există, este șters sau aparține altei companii. → `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/documents/{documentType}/{documentId}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /documents/{documentType}/{documentId}/download-pdf`: Descărcare PDF operationId: `downloadPdf` Returnează PDF-ul documentului codificat în base64 Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentType` | path | string | yes | Tipul documentului; one of `"invoice"`, `"proform"` | | `documentId` | path | integer | yes | ID-ul documentului | Responses: - **200** PDF generat cu succes → `DownloadPdfResponse` - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/documents/{documentType}/{documentId}/download-pdf" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /documents/{documentType}/{documentId}/send-email`: Trimitere email operationId: `sendEmail` Trimite documentul pe email către client. Dacă nu este specificat `to_email`, se folosește email-ul clientului din document. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentType` | path | string | yes | Tipul documentului; one of `"invoice"`, `"proform"` | | `documentId` | path | integer | yes | ID-ul documentului | Request body (JSON): Type: `SendEmailRequest` Responses: - **200** Email trimis cu succes → object - `status` (string) - `message` (string) - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** (`ServerError`) Eroare internă server → object - `status` (string) - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/documents/{documentType}/{documentId}/send-email" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /documents/invoice/{documentId}/payments`: Încasare pe factură operationId: `addInvoicePayment` Înregistrează o încasare. Același `external_id` întoarce încasarea existentă (200, `"existing": true`) fără a compara corpul cererii; altfel 201. Facturile în EUR sunt acceptate (încasarea preia cursul facturii). Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentId` | path | integer | yes | ID-ul documentului | Request body (JSON, required): Type: `InvoicePaymentRequest` Responses: - **200** Încasarea exista deja (`existing: true`) → `DocumentDetail` - **201** Încasare înregistrată (`existing: false`) → `DocumentDetail` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`DocumentNotFound`) Documentul nu există, este șters sau aparține altei companii. → `CodedError` - **409** (`Conflict`) `in_progress` (aceeași cerere este deja în curs sau conflict de concurență; reîncercați identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`). → `CodedError` - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/documents/invoice/{documentId}/payments" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `PUT /documents/proform/{documentId}`: Modificare proformă operationId: `updateProform` Înlocuiește clientul și liniile unei proforme active, nefacturate. Numărul, seria, data și `external_id` rămân. Fără `currency`, proforma își păstrează moneda; cursul BNR se recalculează pentru data proformei. O reducere la nivel de document (pusă din aplicație) se șterge. Fără `save_client`, legătura cu clientul din nomenclator rămâne doar dacă este același cumpărător (același cod fiscal; fără cod fiscal: același email). Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentId` | path | integer | yes | ID-ul documentului | Request body (JSON, required): - `client` (`Client`, required) - `products` (`Product`[], required) - `currency` (string, one of `"RON"`, `"EUR"`) - `observation` (string | null) - `save_client` (boolean, default `false`) - `use_product_name_from_db` (boolean, default `true`) - `inventory_id` (integer) Responses: - **200** Proforma modificată → `DocumentDetail` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`DocumentNotFound`) Documentul nu există, este șters sau aparține altei companii. → `CodedError` - **409** (`Conflict`) `in_progress` (aceeași cerere este deja în curs sau conflict de concurență; reîncercați identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`). → `CodedError` - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X PUT "https://api.rezosoft.ro/v2/external/documents/proform/{documentId}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /documents/proform/{documentId}/cancel`: Anulare proformă operationId: `cancelProform` Dezactivează proforma (`active = false`). Repetarea nu schimbă nimic. 409 `proform_converted` dacă proforma are deja factură. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentId` | path | integer | yes | ID-ul documentului | Responses: - **200** Proforma anulată → `DocumentDetail` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`DocumentNotFound`) Documentul nu există, este șters sau aparține altei companii. → `CodedError` - **409** (`Conflict`) `in_progress` (aceeași cerere este deja în curs sau conflict de concurență; reîncercați identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`). → `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/documents/proform/{documentId}/cancel" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /documents/proform/{documentId}/invoice`: Factură din proformă operationId: `invoiceProform` Generează factura o singură dată. Prima generare răspunde **201** cu `"existing": false`; un apel repetat întoarce aceeași factură (**200**, `"existing": true`) și înregistrează doar plățile care lipsesc. Factura primește `external_id`-ul proformei. - Când factura este creată de apel, plățile se verifică înainte de generare (moneda, suma lor față de totalul proformei + 1, `external_id` nefolosit pe alt document): o plată greșită oprește apelul înainte de a se crea factura sau a se mișca stocul. Conversia și plățile sunt atomice. - Când factura există deja, fiecare plată se înregistrează independent (idempotent după `external_id`). - Un `external_id` de plată folosit deja pe alt document dă 422 de validare pe `external_id`. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `documentId` | path | integer | yes | ID-ul documentului | Request body (JSON): - `update_stock` (boolean, default `false`): Scade stocul la generarea facturii - `payments` (`InvoicePaymentRequest`[]): Încasări de înregistrat pe factură, cu `external_id` distincte Example: ```json { "update_stock": true, "payments": [ { "external_id": "PAY-1001", "type": "bank", "date_at": "2026-10-07", "amount": 242, "currency": "RON" } ] } ``` Responses: - **200** Factura exista deja (`existing: true`) → `DocumentDetail` - **201** Factura a fost generată (`existing: false`) → `DocumentDetail` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`DocumentNotFound`) Documentul nu există, este șters sau aparține altei companii. → `CodedError` - **409** (`Conflict`) `in_progress` (aceeași cerere este deja în curs sau conflict de concurență; reîncercați identic) sau stare incompatibilă (`proform_converted`, `proform_inactive`, `invoice_inactive`). → `CodedError` - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/documents/proform/{documentId}/invoice" \ -H "x-api-key: $REZOSOFT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"update_stock":true,"payments":[{"external_id":"PAY-1001","type":"bank","date_at":"2026-10-07","amount":242,"currency":"RON"}]}' ``` ### Note de recepție (NIR) Creare, listare, vizualizare și descărcare PDF pentru note de recepție (NIR). #### `POST /reception-notes`: Creare notă de recepție (NIR) operationId: `createReceptionNote` Creează o notă de recepție. Furnizorul este identificat după cod fiscal (sau nume) și este creat automat dacă nu există. Produsele sunt identificate după product_id sau code în gestiunea indicată (câmpul code al produsului este folosit și drept cod de bare); cu create_missing_products=true produsele lipsă sunt create automat, altfel cererea este respinsă cu lista lor. Stocul este actualizat identic cu fluxul din aplicație (inclusiv sincronizare POS și proces-verbal de modificare preț la gestiunile cu amănuntul, dacă trimiteți un preț de vânzare diferit). Seria implicită este folosită dacă nu trimiteți document_series_id, iar numărul este alocat automat. Pentru gestiunile cu amănuntul (tip global) se recomandă trimiterea sale_price_with_vat pentru fiecare produs. Request body (JSON, required): Type: `CreateReceptionNoteRequest` Example: ```json { "inventory_id": 12, "date_at": "2026-08-27", "supporting_document": "invoice", "supporting_document_number": "F-1234", "supporting_document_date": "2026-08-25", "supplier": { "fiscal_code": "RO12345678", "name": "Furnizor Exemplu SRL" }, "create_missing_products": false, "products": [ { "code": "P001", "quantity": 10, "price_without_vat": 5.5, "vat_percent": 21 } ], "paid": { "document": "receipt", "number": "CH-1", "date": "2026-08-27", "value": 65.45 } } ``` Responses: - **201** Nota de recepție a fost creată → `CreateReceptionNoteResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **422** Date invalide sau produse negăsite în gestiune (cu create_missing_products=false răspunsul include missing_products). → object - `status` (string) - `message` (string) - `errors` (object): Erori de validare per câmp (opțional) - `missing_products` (object[]): Produsele care nu au fost găsite în gestiune (opțional) - `index` (integer) - `product_id` (integer | null) - `code` (string | null) - `name` (string | null) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/reception-notes" \ -H "x-api-key: $REZOSOFT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"inventory_id":12,"date_at":"2026-08-27","supporting_document":"invoice","supporting_document_number":"F-1234","supporting_document_date":"2026-08-25","supplier":{"fiscal_code":"RO12345678","name":"Furnizor Exemplu SRL"},"create_missing_products":false,"products":[{"code":"P001","quantity":10,"price_without_vat":5.5,"vat_percent":21}],"paid":{"document":"receipt","number":"CH-1","date":"2026-08-27","value":65.45}}' ``` #### `GET /reception-notes`: Listare note de recepție operationId: `getReceptionNotes` Returnează lista paginată a notelor de recepție (NIR), proiecție scurtă. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `period_from` | query | string (date) | no | Filtrează după date_at >= (YYYY-MM-DD). | | `period_to` | query | string (date) | no | Filtrează după date_at <= (YYYY-MM-DD). | | `supplier_id` | query | integer | no | ID-ul furnizorului (din tabela suppliers). | | `supplier_fiscal_code` | query | string | no | Cod fiscal furnizor (egalitate, case-insensitive). | | `inventory_id` | query | integer | no | ID-ul gestiunii. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Rezultate per pagină (max 100). | Responses: - **200** Lista de note de recepție → `ReceptionNoteListResponse` - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reception-notes" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reception-notes/{id}`: Vizualizare notă de recepție operationId: `getReceptionNote` Returnează detaliile complete ale unei note de recepție: antet, furnizor, gestiune, produse, plăți. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul notei de recepție | Responses: - **200** Notă de recepție → `ReceptionNoteResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reception-notes/{id}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reception-notes/{id}/download-pdf`: Descărcare PDF notă de recepție operationId: `downloadReceptionNotePdf` Returnează PDF-ul notei de recepție codificat în base64. Reutilizează schema DownloadPdfResponse. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul notei de recepție | Responses: - **200** PDF generat cu succes → `DownloadPdfResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** (`ServerError`) Eroare internă server → object - `status` (string) - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reception-notes/{id}/download-pdf" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### e-Factura Facturi primite prin SPV (e-Factura): listare, descărcare PDF și XML UBL. #### `GET /efactura/received`: Listare facturi primite (e-Factura) operationId: `listEfacturaReceived` Returnează lista paginată a documentelor primite prin SPV (facturi și note de credit de la furnizori). Ordonare: date_at descrescător, apoi index descrescător. Câmpurile has_pdf și has_xml indică dacă fișierul este deja stocat; PDF-ul poate fi generat din XML la cerere. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `period_from` | query | string (date) | no | Filtrează după data documentului >= (YYYY-MM-DD). | | `period_to` | query | string (date) | no | Filtrează după data documentului <= (YYYY-MM-DD). | | `supplier_id` | query | integer | no | ID-ul furnizorului. | | `supplier_fiscal_code` | query | string | no | Codul fiscal al furnizorului (potrivire exactă, insensibilă la majuscule). | | `document_type` | query | string | no | Tipul documentului.; one of `"invoice"`, `"credit_note"` | | `search` | query | string | no | Caută în numărul documentului sau în numele furnizorului. | | `page` | query | integer | no | Pagina. | | `per_page` | query | integer | no | Documente per pagină (max 100). | Responses: - **200** Lista documentelor primite → `EfacturaReceivedListResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/efactura/received" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /efactura/received/{id}/download-pdf`: Descărcare PDF factură primită operationId: `downloadEfacturaReceivedPdf` Returnează PDF-ul documentului primit, codificat în base64. Dacă PDF-ul nu este încă salvat, este generat din XML-ul UBL prin serviciul ANAF; dacă nu există nici XML, răspunsul este 404. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul documentului primit (din listare) | Responses: - **200** PDF disponibil → `DownloadPdfResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **502** Generarea PDF din XML a eșuat (serviciul ANAF a răspuns cu eroare). → `Error` ```bash curl -X GET "https://api.rezosoft.ro/v2/external/efactura/received/{id}/download-pdf" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /efactura/received/{id}/download-xml`: Descărcare XML UBL factură primită operationId: `downloadEfacturaReceivedXml` Returnează XML-ul UBL al documentului primit, codificat în base64, așa cum a fost descărcat din SPV. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul documentului primit (din listare) | Responses: - **200** XML disponibil → `DownloadXmlResponse` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/efactura/received/{id}/download-xml" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Rapoarte Rapoarte POS (încasări, produse vândute, bonuri, raport Z, mese, comenzi, linii anulate), mișcări de stoc și rețete. #### `GET /reports/pos/incomes`: Raport încasări POS operationId: `getPosIncomesReport` Returnează totalurile, defalcarea TVA și rândurile per zi pentru un interval de timp. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `day_start` | query | string | no | Ora la care începe ziua de activitate (HH:MM). 05:00 se aliniază cu datarea raportului Z. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | Responses: - **200** Raport încasări → `PosIncomeReport` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/incomes" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/products`: Raport produse vândute POS operationId: `getPosProductsReport` Returnează totalurile (cantitate + valoare) și lista produselor vândute în interval. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | Responses: - **200** Raport produse → `PosProductReport` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/products" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/receipts`: Raport bonuri POS operationId: `getPosReceiptsReport` Returnează bonurile POS paginate. Folosiți `include=products` pentru a încorpora produsele fiecărui bon. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `kind` | query | string | no | sale (implicit), pending, scrap, return, protocol, all sau listă separată prin virgulă. | | `after_id` | query | integer | no | Cursor: rândurile cu id > after_id, crescător, fără interval implicit. Răspunsul are pagination.next_after_id. | | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Număr rezultate per pagină (1-500). | | `include` | query | string | no | Setați la `products` pentru a include produsele bonului.; one of `"products"` | Responses: - **200** Raport bonuri → `PosReceiptReport` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/receipts" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/z`: Raport Z POS operationId: `getPosZReport` Totaluri zilnice tip Raport Z, defalcate per (zi, POS). Fiecare rând expune și `cash_book_operations.document_number` ca `z_number` atunci când Raportul Z a fost deja generat pentru gestiunea respectivă în acea zi. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `day_start` | query | string | no | Ora la care începe ziua de activitate (HH:MM). 05:00 se aliniază cu datarea raportului Z. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | Responses: - **200** Raport Z → `PosZReport` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/z" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/tables`: Mese deschise (live) operationId: `getPosTables` Snapshot live al meselor din gestiunile accesibile: liniile deschise, totalul mesei și, cu include=orders, comenzile de bucătărie ale șederii curente cu cronologia stărilor. Liniile și comenzile sunt cheiate pe (gestiune, id masă). Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `inventory_id` | query | integer | no | Restrânge la o gestiune. 404 dacă nu este accesibilă. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `include` | query | string | no | Setați la orders pentru comenzile de bucătărie ale șederii curente.; one of `"orders"` | | `only_open` | query | string | no | 1 = doar mesele deschise (cu linii, opened_at sau ospătar); 0 = toată sala.; one of `"0"`, `"1"` | Responses: - **200** Mese deschise (live) → `PosTables` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** Gestiune inaccesibilă - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/tables" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/orders`: Istoric comenzi de bucătărie operationId: `getPosOrders` Istoricul comenzilor trimise la bucătărie din evenimentele de stare: un rând per comandă cu cronologia (new, in_preparation, finalized, picked_up, deleted), ospătarul care a trimis-o și liniile (90 de zile după închiderea mesei). Intervalul se aplică pe new_at. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `inventory_id` | query | integer | no | Restrânge la o gestiune. 404 dacă nu este accesibilă. | | `table_id` | query | integer | no | Id-ul mesei din POS (unic în cadrul gestiunii). | | `waiter_id` | query | integer | no | Utilizatorul POS (ospătarul) care a trimis comanda. | | `delivery_type` | query | string | no | Tipul de livrare al comenzii (ex. pickup, wolt). | | `status` | query | string | no | Ultima stare a comenzii.; one of `"new"`, `"in_preparation"`, `"finalized"`, `"picked_up"`, `"deleted"` | | `after_id` | query | integer | no | Cursor: rândurile cu id > after_id, crescător, fără interval implicit. Răspunsul are pagination.next_after_id. | | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Rânduri per pagină (1-500). | Responses: - **200** Istoric comenzi de bucătărie → `PosOrders` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** Gestiune inaccesibilă - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/orders" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/pos/cancelled-items`: Linii anulate operationId: `getPosCancelledItems` Liniile scoase de pe masă înainte de plată, așa cum le raportează POS-ul (operator și moment, fără legătură cu masa sau bonul). Păstrate un an. Fără updated_since: rândurile nu se modifică. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | | `pos_id` | query | integer | no | ID-ul POS-ului (user_id). Omiterea returnează toate POS-urile accesibile. | | `inventory_id` | query | integer | no | Restrânge la o gestiune. 404 dacă nu este accesibilă. | | `after_id` | query | integer | no | Cursor: rândurile cu id > after_id, crescător, fără interval implicit. Răspunsul are pagination.next_after_id. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Rânduri per pagină (1-500). | Responses: - **200** Linii anulate → `PosCancelledItems` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** Gestiune inaccesibilă - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/pos/cancelled-items" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /reports/stock/movements`: Mișcări de stoc operationId: `getStockMovements` Jurnalul product_operations al gestiunilor accesibile, interval pe date_at. Vânzările POS nu sunt în jurnal (se citesc din reports/pos/receipts); consumul pe rețete se scrie la raportul Z. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `from` | query | string | no | Început interval (Y-m-d H:i:s sau ISO 8601, ora României). Are prioritate față de period_*. Doar from = fără limită superioară. | | `to` | query | string | no | Sfârșit interval, inclusiv. O dată fără oră înseamnă ziua întreagă (până la 23:59:59). | | `period_from` | query | string (date) | no | Data de început (YYYY-MM-DD). Default: azi. | | `period_to` | query | string (date) | no | Data de sfârșit (YYYY-MM-DD). Default: azi. | | `inventory_id` | query | integer | no | Restrânge la o gestiune. 404 dacă nu este accesibilă. | | `product_id` | query | integer | no | Doar un produs. | | `document_type` | query | string | no | Tipul documentului sursă al mișcării.; one of `"reception_note"`, `"invoice"`, `"delivery_note"`, `"import"`, `"consumption_receipt"`, `"transfer_receipt"`, `"inventory_stock"`, `"damage_report"`, `"production"`, `"transfer_note"` | | `after_id` | query | integer | no | Cursor: rândurile cu id > after_id, crescător, fără interval implicit. Răspunsul are pagination.next_after_id. | | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Rânduri per pagină (1-500). | Responses: - **200** Mișcări de stoc → `StockMovements` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** Gestiune inaccesibilă - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/reports/stock/movements" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /recipes`: Rețete operationId: `getRecipes` Rețeta fiecărui produs finit cu materialele ei; inventory_id se potrivește pe gestiunea sursă sau destinație a șablonului de producție. updated_since include rețetele șterse, cu deleted_at. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `product_id` | query | integer | no | Doar un produs. | | `inventory_id` | query | integer | no | Restrânge la o gestiune. 404 dacă nu este accesibilă. | | `updated_since` | query | string | no | Rândurile modificate de la moment (inclusiv ștergeri, cu deleted_at). Folosiți server_time din răspunsul anterior. | | `page` | query | integer | no | Pagina curentă. | | `per_page` | query | integer | no | Rânduri per pagină (1-500). | Responses: - **200** Rețete → `Recipes` - **400** Parametru invalid - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** Gestiune inaccesibilă - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) - **500** Date POS indisponibile temporar ```bash curl -X GET "https://api.rezosoft.ro/v2/external/recipes" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Județe și localități Județe și localități #### `GET /map/counties`: Lista județe operationId: `mapCounties` Returnează toate județele disponibile cu id, nume și cod Responses: - **200** Lista județe → object - `status` (string) - `counties` (object[]) - `id` (integer) - `name` (string) - `code` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/map/counties" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /map/cities`: Lista localități per județ operationId: `mapCities` Returnează localitățile dintr-un județ. Județul poate fi identificat prin county_id, county_code sau county_text (prioritate în această ordine). Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `county_id` | query | integer | no | ID-ul județului (prioritate maximă) | | `county_code` | query | string | no | Codul județului (ex: TM, BV, CJ) | | `county_text` | query | string | no | Numele județului (căutare parțială) | Responses: - **200** Lista localități → object - `status` (string) - `county` (object) - `id` (integer) - `name` (string) - `code` (string) - `cities` (object[]) - `id` (integer) - `name` (string) - `type` (string | null): Tipul localității - **400** (`BadRequest`) Date invalide sau lipsă → `Error` - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/map/cities" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` ### Webhook-uri Webhook-uri semnate pentru POS: RezoSoft trimite către URL-ul dvs. un eveniment (`pos.receipt.*`, `pos.order.*`, `pos.table.updated`, `product.*`) cu semnătură HMAC-SHA256, reîncercări automate și jurnal de livrări. Webhook-urile se administrează cu endpoint-urile `/webhooks` (creare, test, rotire secret, jurnal, retrimitere) sau din aplicație; evenimentele trimise sunt descrise mai jos. **Integrare veche:** webhook-ul de produs (`product-update`) trimite cereri cu header-ul `X-Security-Key` când se modifică un produs sau stocul. Rămâne disponibil pentru integrările existente (pluginul WooCommerce). Versiunea 1.5 a pluginului folosește webhook-urile semnate. #### `GET /webhooks/events`: Evenimente disponibile operationId: `listWebhookEvents` Catalogul tipurilor de evenimente la care se poate abona un webhook. Responses: - **200** Catalog → object - `status` (string) - `events` (object[]) - `type` (string) - `label` (string) - `group` (string) - `group_label` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/webhooks/events" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /webhooks`: Listare webhook-uri operationId: `listWebhooks` Webhook-urile companiei cheii API, cele mai noi primele. O cheie restricționată la anumite gestiuni vede doar webhook-urile limitate la gestiunile ei (nu și pe cele pentru toate gestiunile). Secretul nu este returnat. Responses: - **200** Lista → object - `status` (string) - `webhooks` (`WebhookEndpoint`[]) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/webhooks" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks`: Creare webhook operationId: `createWebhook` Creează un webhook. Pentru o cheie restricționată la anumite gestiuni, `inventory_ids` trebuie să fie în interiorul lor (omis = toate gestiunile cheii). Secretul (`whsec_…`) se returnează o singură dată. Request body (JSON, required): Type: `WebhookEndpointInput` Responses: - **201** Webhook creat → object - `status` (string) - `webhook` (`WebhookEndpoint`) - `secret` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /webhooks/{id}`: Detaliu webhook operationId: `getWebhook` Un webhook al companiei. Pentru o cheie restricționată, un webhook care nu este limitat la gestiunile ei răspunde 404 aici și la toate operațiile de mai jos. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Webhook → object - `status` (string) - `webhook` (`WebhookEndpoint`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/webhooks/{id}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `PATCH /webhooks/{id}`: Modificare webhook operationId: `updateWebhook` Modifică numele, URL-ul, evenimentele sau gestiunile; câmpurile omise rămân neschimbate. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Request body (JSON, required): Type: `WebhookEndpointInput` Responses: - **200** Webhook modificat → object - `status` (string) - `webhook` (`WebhookEndpoint`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X PATCH "https://api.rezosoft.ro/v2/external/webhooks/{id}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `DELETE /webhooks/{id}`: Ștergere webhook operationId: `deleteWebhook` Șterge webhook-ul; nu se mai trimit evenimente. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Șters → object - `status` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X DELETE "https://api.rezosoft.ro/v2/external/webhooks/{id}" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/pause`: Pauză webhook operationId: `pauseWebhook` Oprește trimiterea. Evenimentele care apar cât webhook-ul este în pauză nu se păstrează și nu se trimit după reluare; livrările aflate deja în așteptare pleacă după reluare. Recuperați perioada cu `updated_since` pe rapoartele POS. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Webhook în pauză → object - `status` (string) - `webhook` (`WebhookEndpoint`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/pause" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/resume`: Reluare webhook operationId: `resumeWebhook` Reactivează un webhook în pauză sau dezactivat automat. Se trimit evenimentele apărute de la reactivare; cele apărute cât webhook-ul a fost în pauză sau dezactivat nu se păstrează (recuperați-le cu `updated_since` pe rapoartele POS). Livrările aflate deja în așteptare pleacă din nou; la reactivarea unui webhook dezactivat automat se reiau și livrările epuizate (`dead`) din ultimele 7 zile. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Webhook activ → object - `status` (string) - `webhook` (`WebhookEndpoint`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/resume" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/rotate-secret`: Rotire secret operationId: `rotateWebhookSecret` Generează un secret nou; cel vechi rămâne valid 24 de ore. Secretul (`whsec_…`) se returnează o singură dată. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Secret nou → object - `status` (string) - `secret` (string) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/rotate-secret" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/test`: Trimitere test operationId: `testWebhook` Trimite imediat un eveniment `webhook.test` (`data`: `{"ping": true}`) și returnează rezultatul livrării. Nu contează la dezactivarea automată. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Rezultatul livrării → object - `status` (string) - `delivery` (`WebhookDelivery`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/test" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `GET /webhooks/{id}/deliveries`: Jurnal livrări operationId: `listWebhookDeliveries` Livrările webhook-ului, cele mai noi primele. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | | `status` | query | string | no | Filtru după starea livrării.; one of `"pending"`, `"delivering"`, `"succeeded"`, `"failed"`, `"dead"` | | `event_type` | query | string | no | one of `"pos.receipt.created"`, `"pos.receipt.updated"`, `"pos.receipt.voided"`, `"pos.order.created"`, `"pos.order.status_changed"`, `"pos.table.updated"`, `"product.created"`, `"product.updated"`, `"product.deleted"`, `"product.stock_changed"` | | `page` | query | integer | no | | | `per_page` | query | integer | no | | Responses: - **200** Livrări → object - `status` (string) - `deliveries` (`WebhookDelivery`[]) - `pagination` (object) - `page` (integer) - `per_page` (integer) - `total` (integer) - `last_page` (integer) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X GET "https://api.rezosoft.ro/v2/external/webhooks/{id}/deliveries" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/deliveries/retry-failed`: Retrimite livrările eșuate operationId: `retryFailedWebhookDeliveries` Programează din nou toate livrările eșuate ale webhook-ului. Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | Responses: - **200** Număr livrări reprogramate → object - `status` (string) - `count` (integer) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/deliveries/retry-failed" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### `POST /webhooks/{id}/deliveries/{deliveryId}/retry`: Retrimite o livrare operationId: `retryWebhookDelivery` Retrimite o singură livrare. O livrare în curs de trimitere (`delivering`) sau o trimitere de test (`webhook.test`) nu se poate retrimite (422). Parameters: | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `id` | path | integer | yes | ID-ul webhook-ului. | | `deliveryId` | path | integer | yes | | Responses: - **200** Livrarea → object - `status` (string) - `delivery` (`WebhookDelivery`) - **401** (`Unauthorized`) Cheie API invalidă sau lipsă → object - `status` (string) - `message` (string) - **404** (`NotFound`) Resursa nu a fost găsită → object - `status` (string) - `message` (string) - **422** (`ValidationError`) Date invalide. Fie erori de validare pe câmp (`errors`), fie o regulă de business cu `code` (vezi secțiunea Erori). → `ValidationError` | `CodedError` - **429** (`TooManyRequests`) Limita de 150 de cereri pe minut per IP a fost depășită. → object - `message` (string) ```bash curl -X POST "https://api.rezosoft.ro/v2/external/webhooks/{id}/deliveries/{deliveryId}/retry" \ -H "x-api-key: $REZOSOFT_API_KEY" ``` #### Webhook `product-update`: Webhook: Actualizare produs operationId: `productUpdateWebhook` **Depreciat:** folosiți evenimentele `product.*`. Acest webhook este trimis automat de RezoSoft către URL-ul configurat atunci când se modifică un produs sau se actualizează stocul (ex: la emiterea unei facturi cu scădere de stoc). **Configurare** 1. În RezoSoft, mergeți la **Setări → Webhook-uri** 2. Adăugați un webhook nou cu: - **URL**: URL-ul aplicației dvs. care va primi notificările - **Tip**: `product` - **Cheie securitate**: O cheie secretă care va fi trimisă în header-ul `X-Security-Key` - **Gestiune**: Selectați gestiunea pentru care doriți notificări **Autentificare** Webhook-ul trimite header-ul `X-Security-Key` cu valoarea cheii de securitate configurate. Aplicația dvs. trebuie să verifice acest header. **Declanșare** Webhook-ul este trimis automat când: - Se creează un produs nou (`action: create`) - Se modifică un produs sau se actualizează stocul (`action: update`) - Se șterge un produs (`action: delete`) **Note importante despre câmpuri** - **`price`**: Conține `price_with_vat` dacă firma este plătitoare de TVA, sau `price_without_vat` în caz contrar - **`category`**: `null` dacă produsul nu are categorie asignată - **`vat`**: `null` dacă firma nu este plătitoare de TVA - **`stock`**: Calculat din operațiunile de inventar minus vânzările POS pentru gestiunea configurată **Exemplu integrare WooCommerce** Pentru pluginul **RezoSoft Facturare WooCommerce**, URL-ul webhook-ului este: ``` https://shop.example.com/wp-json/rezosoft/v1/webhook/product ``` Cheie securitate se configurează din **WP Admin → Setări → RezoSoft Facturare → Configurare → Sincronizare stoc**. Payload (JSON, required): Type: `WebhookProductPayload` Example: ```json { "action": "create", "product": { "id": 542, "code": "PROD001", "name": "Produs exemplu", "description": "Descriere produs", "price": 119, "product_category_id": 5, "vat_id": 1, "category": { "id": 5, "name": "Categoria 1" }, "vat": 19, "stock": 0 } } ``` Responses: - **200** Webhook procesat cu succes #### Webhook `pos.receipt.created`: Eveniment: Bon creat operationId: `webhook_pos_receipt_created` Se trimite când apare un bon nou în POS. Fiecare cerere este un `POST` JSON cu un singur eveniment (`WebhookEvent`) și header-ele `X-RezoSoft-Event-Id`, `X-RezoSoft-Event-Type`, `X-RezoSoft-Delivery-Id` și `X-RezoSoft-Signature`. **Verificarea semnăturii** Header-ul `X-RezoSoft-Signature` are forma `t=1759838531,v1=` (după rotirea secretului, timp de 24 de ore apar două valori `v1`). Calculați `HMAC-SHA256(secret, t + "." + corpul_brut)` și comparați cu oricare `v1`. Respingeți cererile cu `t` mai vechi de 5 minute. ```php $raw = file_get_contents('php://input'); parse_str(str_replace(',', '&', $_SERVER['HTTP_X_REZOSOFT_SIGNATURE']), $parts); // doar pentru t preg_match_all('/v1=([a-f0-9]{64})/', $_SERVER['HTTP_X_REZOSOFT_SIGNATURE'], $m); $expected = hash_hmac('sha256', $parts['t'] . '.' . $raw, $secret); $valid = abs(time() - (int) $parts['t']) <= 300 && array_filter($m[1], fn ($s) => hash_equals($expected, $s)); ``` ```js const [, t] = header.match(/t=(\d+)/); const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); const valid = Math.abs(Date.now() / 1000 - t) <= 300 && [...header.matchAll(/v1=([a-f0-9]{64})/g)].some(([, s]) => crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))); ``` **Livrare** - Răspundeți cu 2xx în maximum 10 secunde; orice alt cod sau timeout se reîncearcă după 1 min, 5 min, 30 min, 2 h, 6 h, 24 h. - La prima încercare evenimentele aceluiași webhook sosesc în ordine; o livrare reîncercată poate sosi după evenimente mai noi. Ordonați după `occurred_at` și citiți starea curentă din `links.self`. Folosiți `X-RezoSoft-Event-Id` pentru a ignora duplicatele. - Corpul este un semnal: citiți starea curentă din `links.self`. - După 100 de eșecuri consecutive sau 3 zile fără nicio livrare reușită, webhook-ul se dezactivează și administratorii primesc un email. - Evenimentele care apar cât un webhook este în pauză sau dezactivat nu se păstrează; livrările aflate deja în așteptare pleacă după reactivare. Recuperați perioada cu `updated_since` pe rapoartele POS. Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `pos.receipt.updated`: Eveniment: Bon modificat operationId: `webhook_pos_receipt_updated` Se trimite când un bon existent este modificat. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `pos.receipt.voided`: Eveniment: Bon anulat operationId: `webhook_pos_receipt_voided` Se trimite când un bon este anulat. `links.self` folosește forma de sincronizare (`updated_since`), pentru că bonurile anulate apar doar acolo. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `pos.order.created`: Eveniment: Comandă nouă operationId: `webhook_pos_order_created` Se trimite când o comandă nouă ajunge în bucătărie (`data.status = new`). Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `pos.order.status_changed`: Eveniment: Comandă: status schimbat operationId: `webhook_pos_order_status_changed` Se trimite la fiecare schimbare de status a unei comenzi (`data.status`, `data.from`). Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `pos.table.updated`: Eveniment: Masă modificată operationId: `webhook_pos_table_updated` Se trimite când o masă se modifică (`data.status`: `open` sau `free`). Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `product.created`: Eveniment: Produs creat operationId: `webhook_product_created` Se trimite când produsul este creat (`data.product_id`). Citiți produsul cu stocul curent din `links.self`. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `product.updated`: Eveniment: Produs modificat operationId: `webhook_product_updated` Se trimite când produsul este modificat (`data.product_id`). Citiți produsul cu stocul curent din `links.self`. Intrările de stoc din NIR, transferuri și alte operații de gestiune sosesc ca `product.updated`; pentru a urmări stocul abonați-vă la ambele evenimente. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `product.deleted`: Eveniment: Produs șters operationId: `webhook_product_deleted` Se trimite când produsul este șters (`data.product_id`). Citiți produsul cu stocul curent din `links.self`. Pentru `product.deleted` linkul răspunde 404. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. #### Webhook `product.stock_changed`: Eveniment: Stoc modificat operationId: `webhook_product_stock_changed` Se trimite când stocul se modifică (`data.product_id`). Citiți produsul cu stocul curent din `links.self`. Intrările de stoc din NIR, transferuri și alte operații de gestiune sosesc ca `product.updated`; pentru a urmări stocul abonați-vă la ambele evenimente. Header-ele, verificarea semnăturii și regulile de reîncercare sunt descrise la evenimentul „Bon creat” (`pos.receipt.created`). Payload (JSON, required): Type: `WebhookEvent` Responses: - **200** Orice răspuns 2xx confirmă livrarea. ## Schemas Referenced above as `SchemaName`. Types: `|` separates alternatives, `[]` is an array, `null` means the field can be null. ### DocumentTemplate - `id` (integer): ID-ul template-ului - `name` (string): Numele template-ului - `default` (integer): Template implicit (1 = da, 0 = nu) ### DocumentSeries - `id` (integer): ID-ul seriei - `name` (string): Numele/Prefixul seriei - `delimiter` (string | null): Delimitator între prefix și număr - `default` (integer): Serie implicită (1 = da, 0 = nu) ### Inventory - `id` (integer): ID-ul gestiunii - `name` (string): Numele gestiunii - `type` (string): Tipul gestiunii - `branch_office_id` (integer | null): ID-ul punctului de lucru asociat - `branch_office` (object | null): Informații punct de lucru ### ProductInfo - `id` (integer): ID-ul produsului - `code` (string | null): Codul produsului / barcode - `name` (string): Numele produsului - `price_with_vat` (number | null): Preț cu TVA - `price_without_vat` (number | null): Preț fără TVA - `vat_percentage` (number | null): Procent TVA - `stock` (number | null): Stocul curent (inclus doar dacă with_stock=true) - `type` (string) - `unit` (string) - `internal_code` (string | null): Codul intern al produsului (cod propriu al firmei, separat de codul de bare) - `available_for_sale` (boolean) - `category` (`CategoryRef`) - `department` (`Ref`): Zona de preparare (ecranul de bucătărie se configurează pe departamente) - `updated_at` (string (date-time) | null) - `deleted_at` (string (date-time) | null): Doar în modul updated_since ### Pagination - `page` (integer): Pagina curentă - `per_page` (integer): Produse per pagină - `total` (integer): Total produse - `last_page` (integer): Ultima pagină ### Client - `name` (string, required): Numele clientului - `fiscal_code` (string): Codul fiscal (CUI/CIF) - `email` (string (email)): Adresa de email - `phone` (string): Număr de telefon - `address` (string): Adresa - `county_id` (integer): ID-ul județului din baza de date (prioritate maximă) - `county_code` (string): Codul județului (ex: B, BV, CJ). Se caută în counties.code - `county_text` (string): Numele județului (căutare parțială). Se folosește doar dacă nu sunt furnizate county_id sau county_code - `city_id` (integer): ID-ul orașului din baza de date (prioritate maximă) - `city_text` (string): Numele orașului (căutare parțială). Se folosește doar dacă nu este furnizat city_id - `country_code` (string): Codul țării (ISO 2 litere) ### Product - `code` (string): Codul / barcode-ul produsului, pentru legarea de un produs existent din gestiune. Dacă nu se găsește, linia se emite ca text liber și nu mișcă stocul. - `name` (string, required): Numele produsului - `description` (string): Descrierea produsului - `quantity` (number, required): Cantitatea - `vat_percentage` (number | string, required): Cota TVA în procente (ex. 21, 11, 0); se acceptă și "21%". Cota trebuie să existe în setările companiei, altfel 422. - `price_with_vat` (number, required): Preț unitar cu TVA - `price_without_vat` (number): Preț unitar fără TVA (calculat automat dacă nu e furnizat) - `measuring_unit` (string): Unitatea de măsură ### Payment Când este setat pe un document de tip `invoice`, factura este marcată ca încasată: se creează o încasare (Income) legată de factură, iar `anaf_payment_instrument` al facturii este setat în funcție de `type`. Ignorat pentru `proform`. - `type` (string, required, one of `"bank"`, `"pos_receipt"`, `"bank_card"`, `"online_card"`, `"post"`, `"cec"`, `"ticket_order"`, `"repayment"`, `"other_income"`): Tipul încasării (folosit pentru a determina automat `anaf_payment_instrument`) - `value` (number): Opțional. Valoarea încasării. Dacă nu este furnizată, se folosește totalul facturii. - `number` (string): Opțional. Număr de referință (ex. OP, ID tranzacție), salvat pe încasare. - `description` (string): Opțional. Descrierea încasării (câmpul `representing`). Dacă nu este furnizată, se folosește ` `. ### CreateDocumentRequest - `document_type` (string, required, one of `"invoice"`, `"proform"`): Tipul documentului - `document_series_id` (integer, required): ID-ul seriei de documente - `document_template_id` (integer, required): ID-ul template-ului - `inventory_id` (integer): ID-ul gestiunii pentru scădere stoc - `observation` (string): Observații pe document - `client` (`Client`, required) - `save_client` (boolean, default `false`): Salvează clientul în baza de date - `products` (`Product`[], required) - `payment` (`Payment`): Încasarea facturii, înregistrată în același apel. Recomandat pentru comenzi deja plătite (ex. magazin online cu plată cu cardul: `{"type": "online_card"}`): emiterea și încasarea rămân un singur apel idempotent. Pentru plăți ulterioare folosiți `POST /documents/invoice/{id}/payments`. - `use_product_name_from_db` (boolean, default `true`): Folosește numele produsului din DB (după cod) - `update_stock` (boolean, default `false`): Scade stocul din gestiunea `inventory_id`. Fiecare linie se leagă de un produs după `product_id` sau `code`; o linie fără produs găsit se emite ca text liber și nu mișcă stocul. Stocul insuficient nu blochează emiterea: stocul poate deveni negativ. - `send_email_after_issue` (boolean, default `false`): Trimite documentul pe email după emitere - `external_id` (string): ID extern pentru referință (max. 128). Obligatoriu cu `idempotent: true`. - `currency` (string, one of `"RON"`, `"EUR"`, default `"RON"`): Moneda documentului. Pentru EUR se folosește cursul BNR al zilei. - `idempotent` (boolean, default `false`): Cu `true` (și `external_id` obligatoriu) documentul se emite o singură dată pe cheia (companie, tip document, `external_id`). O cerere repetată întoarce documentul existent cu `"existing": true`: corpul nou nu se compară cu primul și este ignorat, nu se scrie nimic și stocul nu se mai mișcă. Cheia nu expiră; după ștergerea documentului, același `external_id` emite unul nou. ### CreateDocumentResponse - `status` (string) - `id` (integer): ID-ul documentului creat - `key` (string): Cheia unică pentru acces public - `number` (string): Numărul complet al documentului - `total` (number): Valoarea totală - `pdf_url` (string): URL pentru descărcare PDF publică - `currency` (string) - `existing` (boolean): Doar cu `idempotent: true`: `false` la prima emitere, `true` când documentul exista deja pentru acest `external_id`. ### DownloadPdfResponse - `status` (string) - `filename` (string): Numele fișierului PDF - `content` (string): Conținutul PDF codificat în base64 - `content_type` (string): Tipul MIME ### DocumentClient - `name` (string | null) - `fiscal_code` (string | null) - `email` (string | null) ### DocumentListItem - `id` (integer) - `type` (string, one of `"invoice"`, `"proform"`) - `number` (string): Serie + delimitator + număr - `series_name` (string) - `series_number` (string) - `date_at` (string (date)) - `client` (`DocumentClient`) - `currency` (string) - `currency_rate` (number | null) - `total_net` (number) - `total_vat` (number) - `total` (number) - `active` (boolean) - `payment_status` (integer): Doar pentru facturi - `efactura_status` (string): Doar pentru facturi - `created_at` (string (date-time)) ### DocumentListResponse - `status` (string) - `documents` (`DocumentListItem`[]) - `pagination` (`Pagination`) ### EfacturaSupplier - `id` (integer) - `name` (string | null) - `fiscal_code` (string | null) ### EfacturaReceivedItem - `id` (integer) - `document_index` (string): Indexul de încărcare ANAF - `download_index` (string | null): Indexul de descărcare ANAF - `document_type` (string, one of `"invoice"`, `"credit_note"`) - `document_number` (string | null) - `date_at` (string (date) | null) - `supplier` (`EfacturaSupplier`) - `total` (number | null) - `currency` (string | null) - `status` (string | null) - `reception_note_id` (integer | null): NIR-ul asociat, dacă există - `has_pdf` (boolean) - `has_xml` (boolean) - `received_at` (string (date-time) | null): Momentul preluării din SPV ### EfacturaReceivedListResponse - `status` (string) - `efactura_documents` (`EfacturaReceivedItem`[]) - `pagination` (`Pagination`) ### DownloadXmlResponse - `status` (string) - `filename` (string): Numele fișierului XML - `content` (string): Conținutul XML codificat în base64 - `content_type` (string): Tipul MIME ### ReceptionNoteListItem - `id` (integer) - `date_at` (string (date)) - `reception_number` (string) - `document_series` (object) - `name` (string | null) - `delimiter` (string | null) - `number` (integer | null) - `supporting_document` (object) - `type` (string | null) - `number` (string | null) - `date_at` (string (date) | null) - `due_date_at` (string (date) | null) - `currency` (string) - `total_invoice` (number (float) | null) - `total_invoice_vat` (number (float) | null) - `total_acquisition` (number (float) | null) - `total_sale` (number (float) | null) - `inventory` (object | null) - `id` (integer) - `name` (string) - `supplier` (object | null) - `id` (integer | null) - `name` (string) - `fiscal_code` (string | null) ### ReceptionNoteListResponse - `status` (string) - `reception_notes` (`ReceptionNoteListItem`[]) - `pagination` (`Pagination`) ### ReceptionNoteSupplier - `id` (integer | null) - `name` (string) - `fiscal_code` (string | null) - `trade_register_number` (string | null) - `address` (string | null) - `city` (string | null) - `county` (string | null) - `country` (string | null) ### ReceptionNoteProduct - `id` (integer) - `name` (string) - `code` (string | null) - `measuring_unit` (string | null) - `quantity` (number (float)) - `acquisition_vat_percentage` (number (float)) - `sale_vat_percentage` (number (float) | null) - `acquisition_price_without_vat` (number (float) | null) - `acquisition_price_with_vat` (number (float) | null) - `sale_price_without_vat` (number (float) | null) - `sale_price_with_vat` (number (float) | null) - `series_name` (string | null) ### ReceptionNotePayment - `id` (integer) - `date_at` (string (date) | null) - `value` (number (float) | null) - `document` (string | null) - `document_number` (string | null) ### ReceptionNote Type: `ReceptionNoteListItem` & object ### ReceptionNoteResponse - `status` (string) - `reception_note` (`ReceptionNote`) ### CreateProductRequest - `inventory_id` (integer, required): ID-ul gestiunii în care se creează produsul - `name` (string, required): Numele produsului - `code` (string | null): Codul produsului (folosit și drept cod de bare). Generat automat dacă lipsește. Trebuie să fie unic în gestiune. - `type` (string | null, one of `"unspecified"`, `"raw_material"`, `"cash_advanced"`, `"other_material"`, `"finite_product"`, `"pos_product"`, `"goods"`, `"inventory_item"`, `"bought_services"`, `"packing"`, `"services"`, `null`): Tipul produsului (implicit goods). - `unit` (string | null): Unitatea de măsură (implicit buc). - `vat_percent` (number, required): Procent TVA — cota respectivă trebuie să existe pentru companie. - `price_with_vat` (number | null): Preț de vânzare cu TVA. Obligatoriu dacă lipsește price_without_vat. - `price_without_vat` (number | null): Preț de vânzare fără TVA. Obligatoriu dacă lipsește price_with_vat. - `description` (string | null): Descrierea produsului - `min_stock` (number | null): Stoc minim pentru alerte - `internal_code` (string | null): Codul intern al produsului (cod propriu al firmei, separat de codul de bare) - `available_for_sale` (boolean): Disponibil pentru vânzare (implicit true) ### CreateReceptionNoteRequest - `inventory_id` (integer, required): ID-ul gestiunii (vezi GET /inventories). - `date_at` (string (date), required): Data notei de recepție. - `document_series_id` (integer | null): Seria pentru NIR (opțional, implicit seria default). Numărul este alocat automat. - `supporting_document` (string, required, one of `"invoice"`, `"delivery_note"`, `"pos_receipt"`, `"pos_receipt_with_fiscal_code"`, `"trading_card"`, `"reverse_taxation"`, `"purchase_bill"`, `"nontaxable_operation"`, `"contract"`, `"exception_from_amef"`, `"simplified_invoice"`, `"issued_behalf_supplier"`, `"no_partial_deduction"`, `"no_total_deduction"`, `"article_311"`, `"article_312"`, `"production_receipt"`, `"test_document"`): Tipul documentului însoțitor. Trimiteți cheia (ex: `invoice`, `delivery_note`), nu eticheta. Denumirea în română (ex: `Factura`, `Aviz`) este acceptată pentru compatibilitate, dar se salvează tot cheia. - `supporting_document_number` (string, required): Numărul documentului însoțitor. - `supporting_document_date` (string (date) | null): Data documentului însoțitor (implicit date_at). - `supporting_document_due_date` (string (date) | null): Scadența (opțional). - `currency` (string, default `"RON"`): Moneda (3 litere). Pentru altă monedă decât RON sunt obligatorii currency_date și currency_rate. - `currency_date` (string (date) | null) - `currency_rate` (number | null) - `vat_payer` (boolean | null): Documentul însoțitor are TVA. Implicit true dacă cel puțin un produs are cotă TVA > 0. - `reverse_vat_charge` (boolean, default `false`): Taxare inversă. - `observation` (string | null) - `supplier` (object, required): Furnizorul este căutat după cod fiscal (comparație doar pe cifre), apoi după nume; dacă nu există, este creat (name devine obligatoriu). - `fiscal_code` (string | null) - `name` (string | null): Obligatoriu doar la crearea unui furnizor nou. - `trade_register_number` (string | null) - `address` (string | null) - `county` (string | null): Numele județului (pentru furnizori noi). - `city` (string | null): Numele localității (pentru furnizori noi). - `phone` (string | null) - `email` (string | null) - `create_missing_products` (boolean, required): true: produsele negăsite în gestiune sunt create automat (necesită name și vat_percent; fără code se generează automat un cod de bare EAN-13). false: cererea este respinsă cu lista missing_products. - `products` (object[], required) - `product_id` (integer | null): Identificare după ID produs. - `code` (string | null): Identificare după cod produs. - `name` (string | null): Numele produsului (obligatoriu la creare). - `quantity` (number, required): Cantitatea recepționată (în unitatea principală a produsului). - `price_without_vat` (number | null): Preț achiziție fără TVA (obligatoriu dacă lipsește price_with_vat). - `price_with_vat` (number | null): Preț achiziție cu TVA (obligatoriu dacă lipsește price_without_vat). - `vat_percent` (number | null): Cota TVA (ex: 21). Implicit cota produsului; obligatorie la crearea unui produs nou. - `sale_price_with_vat` (number | null): Preț de vânzare cu TVA. Recomandat pentru gestiunile cu amănuntul; dacă diferă de prețul curent, se generează proces-verbal de modificare preț. - `sale_price_without_vat` (number | null) - `unit` (string | null): Unitatea de măsură la crearea unui produs nou (implicit buc). - `type` (string | null, one of `"unspecified"`, `"raw_material"`, `"cash_advanced"`, `"other_material"`, `"finite_product"`, `"pos_product"`, `"goods"`, `"inventory_item"`, `"bought_services"`, `"packing"`, `"services"`, `null`): Tipul produsului la creare (implicit goods). - `paid` (object | null): Plata documentului (opțional). - `document` (string, one of `"receipt"`, `"payment_order"`, `"pos_receipt"`, `"postal_mandate"`, `"card"`, `"cec"`, `"order_ticket"`) - `number` (string) - `date` (string (date)): Implicit date_at. - `value` (number) ### CreateReceptionNoteResponse - `status` (string) - `message` (string) - `reception_note` (`ReceptionNote`) ### SendEmailRequest - `to_email` (string (email)): Adresa de email destinatar (opțional, implicit email client) - `subject` (string): Subiect personalizat (opțional) - `message` (string): Mesaj personalizat (opțional) ### WebhookProductPayload Payload-ul trimis de webhook la actualizarea unui produs - `action` (string, one of `"create"`, `"update"`, `"delete"`): Tipul acțiunii - `product` (object) - `id` (integer): ID-ul produsului în RezoSoft - `code` (string): Codul produsului (SKU) - `name` (string): Numele produsului - `description` (string | null): Descrierea produsului - `price` (number): Prețul produsului (cu/fără TVA în funcție de platitor TVA) - `product_category_id` (integer | null): ID-ul categoriei (vezi și category) - `vat_id` (integer | null): ID-ul cotei TVA (vezi și vat) - `category` (object | null): Categoria produsului - `id` (integer) - `name` (string) - `vat` (integer | null): Procent TVA (null dacă firma nu e plătitoare de TVA) - `stock` (number): Stocul curent al produsului (calculat din operațiuni inventar și vânzări POS) ### Error - `status` (string) - `message` (string) ### PosVatBreakdownRow - `day` (string (date)) - `vat_percentage` (number) - `total_sale` (number): Total vândut (cu TVA) pentru această cotă - `vat_value` (number): Valoarea TVA inclusă în total_sale (calculată ca total_sale × vat_percentage / (100 + vat_percentage)) ### PosIncomeReport - `status` (string) - `totals` (object) - `receipts` (integer) - `total` (number) - `cash` (number) - `card` (number) - `online` (number) - `voucher` (number) - `ticket` (number) - `discount` (number) - `tip_value` (number) - `sgr_quantity` (number): Cantitate ambalaje SGR vândute - `vat_breakdown` (`PosVatBreakdownRow`[]) - `daily_rows` (object[]) - `day` (string (date)) - `total` (number) - `cash` (number) - `card` (number) - `online` (number) - `voucher` (number) ### PosProductReport - `status` (string) - `totals` (object) - `quantity` (number) - `total` (number) - `products` (object[]) - `code` (string) - `name` (string) - `quantity` (number) - `unit_price` (number) - `total` (number) ### PosReceiptProduct - `code` (string) - `name` (string) - `quantity` (number) - `unit_price` (number): Preț unitar efectiv (total / cantitate, după discount) - `total` (number): Preț total linie (cantitate × preț unitar - discount) - `vat_percentage` (number) - `product_id` (integer | null) - `unit` (string | null) - `list_price` (number): Prețul de listă din POS, înainte de discount - `discount` (number) - `discount_added_by` (string | null) - `vat_value` (number) - `type` (string | null) - `category` (`CategoryRef`) - `department` (`Ref`) ### PosReceiptReport - `status` (string) - `server_time` (string (date-time)) - `receipts` (object[]) - `id` (integer) - `receipt_number` (string) - `created_at` (string (date-time)) - `total` (number) - `cash` (number) - `card` (number) - `online` (number) - `voucher` (number) - `ticket` (number) - `operator` (string) - `fiscal_code` (string): CIF-ul clientului (dacă a fost furnizat pe bon) - `pos_name` (string): Prezent doar când sunt mai multe POS-uri - `kind` (string, one of `"sale"`, `"pending"`, `"scrap"`, `"return"`, `"protocol"`): După sent_to_cash_register: 1,2 sale; 0 pending; -1 scrap; -2 return; -3 protocol - `pos_id` (integer | null) - `inventory_id` (integer | null) - `table` (`Ref`) - `delivery_type` (string | null) - `custom_payment_1` (number) - `custom_payment_2` (number) - `custom_payment_3` (number) - `discount` (object) - `general` (string | null) - `general_value` (number) - `items_value` (number) - `tip` (object) - `raw` (string | null) - `value` (number) - `vat_breakdown` (object[]) - `vat_percentage` (number) - `gross` (number) - `vat_value` (number) - `invoice` (object | null) - `id` (integer) - `number` (string) - `date_at` (string (date)) - `total` (number) - `updated_at` (string (date-time) | null): Momentul sincronizării / ultimei modificări (server) - `deleted_at` (string (date-time) | null): Doar în modul updated_since - `products` (`PosReceiptProduct`[]): Prezent numai când include=products. - `pagination` (object) - `page` (integer) - `per_page` (integer) - `total` (integer) - `last_page` (integer) ### Ref - `id` (integer) - `name` (string) ### CategoryRef Categoria din POS; null când produsul nu are sau categoria a fost ștearsă. - `id` (integer) - `name` (string) - `parent_id` (integer | null) - `order_number` (integer) - `hidden` (boolean) ### CursorPagination Forma paginării în modul after_id. - `per_page` (integer) - `count` (integer) - `next_after_id` (integer | null): null pe ultima pagină ### ReceiptLine - `code` (string | null) - `name` (string) - `quantity` (number) - `unit_price` (number): Preț unitar efectiv (total / cantitate) - `total` (number) - `vat_percentage` (number) - `product_id` (integer | null) - `unit` (string | null) - `list_price` (number): Prețul de listă din POS, înainte de discount - `discount` (number) - `discount_added_by` (string | null) - `vat_value` (number) - `type` (string | null) - `category` (`CategoryRef`) - `department` (`Ref`) ### TableLine - `id` (integer) - `product_id` (integer | null) - `code` (string | null) - `name` (string) - `quantity` (number) - `unit` (string | null) - `list_price` (number) - `discount` (string | null): Valoarea brută din POS: sumă sau procent (ex. 10%) - `discount_added_by` (string | null) - `total` (number) - `vat_percentage` (number) - `type` (string | null) - `category` (`CategoryRef`) - `department` (`Ref`) - `added_by` (string | null): qr_code pentru comenzile clientului - `sgr_package` (integer) ### OrderLine - `table_item_id` (integer | null) - `product_id` (integer | null) - `code` (string | null) - `name` (string) - `quantity` (number) - `unit` (string | null) - `list_price` (number) - `total` (number) - `vat_percentage` (number) - `category` (`CategoryRef`) - `department` (`Ref`) ### Timeline Primul moment în care comanda a atins fiecare stare; null când nu a atins-o. - `new_at` (string (date-time) | null) - `in_preparation_at` (string (date-time) | null) - `finalized_at` (string (date-time) | null) - `picked_up_at` (string (date-time) | null) - `deleted_at` (string (date-time) | null): Scoasă de pe ecranul bucătăriei, de regulă la închiderea mesei ### KitchenOrder - `id` (integer) - `order_number` (string | null) - `status` (string, one of `"new"`, `"in_preparation"`, `"finalized"`, `"picked_up"`) - `delivery_type` (string | null) - `items_count` (integer | null) - `created_at` (string (date-time)) - `updated_at` (string (date-time) | null) - `timeline` (`Timeline`) - `waiter` (`Ref`) - `items` (`OrderLine`[]) ### PosTables - `status` (string) - `server_time` (string (date-time)) - `totals` (object) - `open_tables` (integer) - `open_value` (number): Suma liniilor neîncasate - `tables` (object[]) - `id` (integer): Id-ul mesei din POS, unic în cadrul gestiunii - `name` (string) - `inventory_id` (integer) - `pos_id` (integer): Contul POS care deține sala - `floor` (`Ref`) - `waiter` (`Ref`) - `opened_at` (string (date-time) | null) - `updated_at` (string (date-time) | null) - `general_discount` (string | null) - `tip` (string | null) - `fiscal_code` (string | null) - `total` (number) - `items` (`TableLine`[]) - `orders` (`KitchenOrder`[]): Doar cu include=orders ### PosOrders - `status` (string) - `server_time` (string (date-time)) - `orders` (object[]) - `id` (integer) - `order_number` (string | null) - `inventory_id` (integer | null) - `pos_id` (integer | null) - `table` (`Ref`) - `waiter` (`Ref`) - `delivery_type` (string | null) - `items_count` (integer | null) - `status` (string, one of `"new"`, `"in_preparation"`, `"finalized"`, `"picked_up"`, `"deleted"`) - `timeline` (`Timeline`) - `closed_at` (string (date-time) | null): Închiderea mesei - `items` (`OrderLine`[] | null): null după purjarea rândului (90 de zile de la închidere) - `pagination` (`Pagination` | `CursorPagination`) ### PosCancelledItems - `status` (string) - `server_time` (string (date-time)) - `items` (object[]) - `id` (integer) - `created_at` (string (date-time)) - `pos_id` (integer | null) - `pos_name` (string | null) - `inventory_id` (integer | null) - `operator` (string | null) - `product` (object) - `id` (integer | null) - `code` (string | null) - `name` (string) - `quantity` (number) - `unit` (string | null) - `list_price` (number) - `discount` (number) - `total` (number) - `vat_percentage` (number) - `type` (string | null) - `category` (`CategoryRef`) - `department` (`Ref`) - `pagination` (`Pagination` | `CursorPagination`) ### StockMovements - `status` (string) - `server_time` (string (date-time)) - `movements` (object[]) - `id` (integer) - `date_at` (string (date-time)) - `type` (string, one of `"in"`, `"out"`) - `quantity` (number) - `product` (object) - `id` (integer) - `code` (string | null) - `name` (string) - `unit` (string) - `inventory_id` (integer) - `document` (object) - `type` (string) - `id` (integer | null) - `number` (string | null): Pentru documentele cu serie și pentru inventar - `acquisition_price` (number | null) - `sale_price` (number | null) - `sale_price_without_vat` (number | null) - `created_at` (string (date-time)) - `updated_at` (string (date-time) | null) - `pagination` (`Pagination` | `CursorPagination`) ### Recipes - `status` (string) - `server_time` (string (date-time)) - `recipes` (object[]) - `id` (integer) - `product` (object) - `id` (integer) - `code` (string | null) - `name` (string) - `yield` (object) - `quantity` (number) - `unit` (string) - `decimals` (integer) - `template` (object) - `id` (integer) - `name` (string) - `from_inventory_id` (integer) - `to_inventory_id` (integer | null) - `auto_generate` (string) - `materials` (object[]) - `id` (integer) - `kind` (string, one of `"product"`, `"cost"`) - `product_id` (integer | null) - `code` (string | null) - `name` (string) - `quantity` (number) - `loss_quantity` (number) - `loss_type` (string, one of `"value"`, `"percent"`) - `total_quantity` (number) - `unit` (string) - `price` (number | null) - `updated_at` (string (date-time) | null) - `deleted_at` (string (date-time) | null): Doar în modul updated_since - `pagination` (`Pagination`) ### PosZReport - `status` (string) - `days` (object[]) - `day` (string (date)) - `pos_id` (integer) - `pos_name` (string | null) - `inventory_id` (integer | null): null când POS-ul nu are gestiune mapată sau gestiunea a fost ștearsă. - `inventory_name` (string | null) - `z_number` (string | null): Preluat din `cash_book_operations.document_number` (name='z_report'). null pentru ziua curentă (Raportul Z se generează la trecerea zilei) și pentru zilele fără Raport Z. - `receipts` (integer) - `total` (number) - `cash` (number) - `card` (number) - `online` (number) - `voucher` (number) - `ticket` (number) - `discount` (number): Suma `pos_receipts.general_discount_value` + `pos_receipt_items.discount`. - `tip_value` (number) - `sgr_quantity` (number): Cantitate ambalaje SGR vândute. - `sgr_value` (number): Calculat ca `sgr_quantity * 0.5` (garanția SGR fixă). ### CodedError - `status` (string) - `code` (string): Identificator stabil al erorii - `message` (string) ### ValidationError - `message` (string) - `errors` (object) ### DocumentLine - `product_id` (integer | null) - `code` (string | null) - `name` (string) - `measuring_unit` (string | null) - `quantity` (string): 4 zecimale - `price_without_vat` (string): Preț unitar, 4 zecimale - `vat_percentage` (string) - `total_without_vat` (string): Sumă cu 2 zecimale - `total_vat` (string): Sumă cu 2 zecimale - `total` (string): Sumă cu 2 zecimale ### InvoicePaymentRow - `id` (integer) - `source` (string, one of `"income"`, `"receipt"`) - `type` (string) - `date_at` (string (date)) - `amount` (string): Sumă cu 2 zecimale - `currency` (string) - `external_id` (string | null) ### DocumentDetail Totalurile unei linii sunt după reducerea de pe linie, deci liniile adună totalul documentului (cu excepția unei reduceri la nivel de document, puse din aplicație). - `status` (string) - `id` (integer) - `type` (string, one of `"proform"`, `"invoice"`) - `number` (string) - `series_name` (string) - `series_number` (string): Șirul brut, cu zerourile de la început - `date_at` (string (date)) - `external_id` (string | null) - `active` (boolean): `false` = anulat - `currency` (string) - `currency_rate` (number | null): Doar pentru alte monede decât RON - `client` (object) - `name` (string | null) - `fiscal_code` (string | null) - `email` (string | null) - `total_net` (string): Sumă cu 2 zecimale - `total_vat` (string): Sumă cu 2 zecimale - `total` (string): Sumă cu 2 zecimale - `pdf_url` (string | null): `null` când documentul nu are cheie publică - `lines` (`DocumentLine`[]) - `invoice` (object | null): Doar proformă: factura generată din ea (nu storno, nu ștearsă) - `id` (integer) - `number` (string) - `proform` (object | null): Doar factură: proforma din care provine - `id` (integer) - `number` (string) - `payment_status` (string, one of `"unpaid"`, `"partial"`, `"paid"`): Doar factură - `amount_due` (string): Doar factură: rest de plată, în moneda facturii - `paid_amount` (string): Doar factură: suma încasată, în moneda facturii - `payments` (`InvoicePaymentRow`[]): Doar factură, în ordinea datei - `existing` (boolean): Doar la facturare proformă și încasare: `true` când rezultatul exista deja ### InvoicePaymentRequest - `external_id` (string, required): Cheia de idempotență a încasării - `type` (string, required, one of `"bank"`, `"pos_receipt"`, `"bank_card"`, `"online_card"`, `"post"`, `"cec"`, `"ticket_order"`, `"repayment"`, `"other_income"`) - `date_at` (string (date), required): Cel mult azi - `amount` (number, required): Max. 2 zecimale; cel mult restul de plată + 1 - `currency` (string, required, one of `"RON"`, `"EUR"`): Trebuie să fie moneda facturii ### WebhookEndpoint - `id` (integer) - `name` (string) - `url` (string (uri)) - `events` (string[]): Tipuri de evenimente sau tipare: `*`, `pos.receipt.*`, `pos.receipt.created`. - `inventory_ids` (integer[] | null): `null` = toate gestiunile. - `status` (string, one of `"active"`, `"paused"`, `"disabled"`) - `consecutive_failures` (integer) - `last_success_at` (string (date-time) | null) - `last_failure_at` (string (date-time) | null) - `disabled_at` (string (date-time) | null) - `disabled_reason` (string | null) - `secret_rotating_until` (string (date-time) | null): Până când se acceptă și secretul vechi, după o rotire. - `created_at` (string (date-time) | null) ### WebhookDelivery - `id` (integer) - `event_id` (string) - `event_type` (string) - `resource_type` (string | null) - `resource_id` (integer | null) - `status` (string, one of `"pending"`, `"delivering"`, `"succeeded"`, `"failed"`, `"dead"`): `failed` se reîncearcă; `dead` = reîncercările s-au epuizat. - `attempts` (integer) - `next_attempt_at` (string (date-time) | null) - `response_code` (integer | null) - `response_excerpt` (string | null) - `error` (string | null) - `duration_ms` (integer | null) - `delivered_at` (string (date-time) | null) - `created_at` (string (date-time) | null) - `payload` (`WebhookEvent`) ### WebhookEvent Corpul fiecărei cereri trimise către webhook: un singur eveniment, fără obiecte încorporate. - `id` (string) - `type` (string, one of `"pos.receipt.created"`, `"pos.receipt.updated"`, `"pos.receipt.voided"`, `"pos.order.created"`, `"pos.order.status_changed"`, `"pos.table.updated"`, `"product.created"`, `"product.updated"`, `"product.deleted"`, `"product.stock_changed"`) - `occurred_at` (string (date-time)) - `company_id` (integer) - `inventory_id` (integer | null) - `data` (object): Bon: `receipt_id`, `kind`, `pos_id`. Comandă: `order_id`, `table_id`, `status` (+ `from` la schimbare de status). Masă: `table_id`, `status`. - `links` (object) - `self` (string (uri)): Cererea cu care citiți starea curentă. ### WebhookEndpointInput - `name` (string, required) - `url` (string (uri), required): Doar `https`, adresă publică. - `events` (string[], required) - `inventory_ids` (integer[] | null): Gestiunile urmărite; omis sau `null` = toate gestiunile cheii.