Stocuri
Stocul per locație pentru fiecare produs, plus o vedere agregată per produs și operațiuni de ajustare a stocului.
Endpoint-uri
| Metodă | Cale | Descriere |
|---|---|---|
| GET | /api/v1/inventory | Listă paginată a rândurilor de stoc (unul per produs+locație); filtre product_id, location_id, sku, low_stock. |
| GET | /api/v1/inventory/summary | Total agregat per produs, peste toate locațiile. |
| POST | /api/v1/inventory/add-stock | Adaugă stoc la un produs într-o locație. |
| POST | /api/v1/inventory/remove-stock | Scoate stoc dintr-un produs la o locație. |
Filtre la listare (GET /api/v1/inventory)
| Parametru | Tip | Note |
|---|---|---|
product_id | integer | Filtrează după produs (products.product_id). |
location_id | integer | Filtrează după locație (locations.id). |
sku | string | Filtrează după SKU-ul produsului. |
low_stock | boolean | true → doar produsele sub min_stock_level (când e configurat). |
per_page | integer | Implicit 100. |
Total agregat (GET /api/v1/inventory/summary)
Un rând per produs, însumat peste toate locațiile. Fiecare rând conține product_id, location_count și vocabularul unic de stoc:
| Câmp | Semnificație |
|---|---|
on_hand | Toate unitățile fizic prezente, în orice locație. |
blocked | Din on_hand, unitățile din locații de tip quarantine, qc_hold, pending_approval sau din locații care nu sunt active. |
reserved | reserved_quantity pe rânduri plus cantitatea sarcinilor de relocare deschise. |
pickable | Unitățile din locații pickable și active (fără cele blocate). |
sellable | Unitățile din locații sellable și active (fără cele blocate). |
available | pickable minus rezervările din locațiile pickable, minim 0. Singurul număr de promis clienților. |
in_transit | Unitățile din relocări cu starea in_transit. |
Câmpurile vechi rămân ca alias-uri: total_quantity = on_hand, total_reserved = reserved, available_quantity = available. Obiectul product inclus aduce și price și price_eur (decimal, ca string), deci prețul e disponibil fără un apel separat per rând.
Fiecare rând din GET /api/v1/inventory conține în plus state (available, reserved, non_pickable sau blocked:<motiv>, cu motivul quarantine, qc_hold, pending_approval ori location_inactive), iar location include type, is_pickable și is_sellable. Filtrul in_stock de pe /products înseamnă acum available > 0. Obiectul product inclus aduce și price și price_eur (decimal, ca string), deci prețul e disponibil fără un apel separat per rând.
| Parametru | Tip | Note |
|---|---|---|
product_id | integer | Filtrează după produs (products.product_id). |
search | string | Caută în name, sku, barcode (aplicat înainte de agregare, deci total reflectă rezultatul). |
in_stock | boolean | 1 → doar produse cu total_quantity > 0; 0 → doar produse cu totalul pe zero. Filtrul e agregat (HAVING), iar total / last_page reflectă setul filtrat. |
per_page | integer | Implicit 100. |
in_stock=0 aici ≠ pe /products
summary se construiește grupând rândurile de inventar, deci un produs fără niciun rând de stoc nu apare deloc aici. in_stock=0 înseamnă „rândurile existente însumează zero", nu „produs niciodată stocat". Pentru a prinde și produsele fără niciun rând, folosește /products?in_stock=0.
Schema răspunsului pentru chei de client
O cheie legată de un client extern citește stocul propriu într-o schemă explicită (scrierea de stoc nu este disponibilă cheilor de client). Cheile tenantului și cheile house păstrează schema de integrare descrisă mai sus.
GET /api/v1/inventory, un rând per locație: product_id, sku, location_code, warehouse ({ "code": "…" } sau null), quantity, available_quantity, state, batch_number, lot_number, expiry_date.
GET /api/v1/inventory/summary, un rând per produs: product_id, sku, on_hand, available, reserved, committed, in_transit.
Exemple (verificate automat față de schemele înghețate):
{
"data": [
{
"product_id": 812,
"sku": "BRK-PAD-001",
"location_code": "CULOAR1-RAFT-3",
"warehouse": { "code": "MAIN" },
"quantity": 20,
"available_quantity": 18,
"state": "available",
"batch_number": "B-2026-06",
"lot_number": null,
"expiry_date": null
}
]
}{
"data": [
{
"product_id": 812,
"sku": "BRK-PAD-001",
"on_hand": 20,
"available": 18,
"reserved": 2,
"committed": 2,
"in_transit": 0
}
]
}Avize de expediție (ASN) pentru chei de client
Pagina Avize de expediție descrie cererile, răspunsurile și anularea.
GET /api/v1/asns, GET /api/v1/asns/{id}, POST /api/v1/asns și POST /api/v1/asns/{id}/cancel răspund, pentru o cheie de client, cu: id, reference, status, expected_on, expected_at, arrived_at, carrier, tracking_number, container_number, seal_number, source, client_confirmed_at, closed_short_at, warehouse ({ "code": "…" } sau null) și lines[] cu id, product_id, sku, expected_quantity, received_quantity, accepted_quantity, damaged_quantity, rejected_quantity, batch_number, expiry_date, origin. Nota de sosire a depozitului, sesiunea de recepție și câmpul notes nu fac parte din răspuns. Cheile tenantului păstrează schema de integrare.
Adăugare stoc
Câmpuri pentru POST /api/v1/inventory/add-stock:
| Câmp | Tip | Obligatoriu | Note |
|---|---|---|---|
product_id | integer | da | Trebuie să existe (products.product_id). |
location_id | integer | da | Trebuie să existe (locations.id). |
quantity | integer ≥ 1 | da | Cantitatea de adăugat. |
batch_number | string ≤ 50 | nu | Lot/șarjă. |
lot_number | string ≤ 50 | nu | |
expiry_date | YYYY-MM-DD | nu | Data de expirare. |
notes | text | nu | Notă liberă, salvată în mișcarea de stoc. |
curl -X POST https://tenant.notsowms.ro/api/v1/inventory/add-stock \
-H "Authorization: Bearer tenant.7c4a8d09ca3762af61e59520943dc26494f8941b" \
-H "Content-Type: application/json" \
-d '{
"product_id": 812,
"location_id": 14,
"quantity": 20,
"batch_number": "B-2026-06",
"notes": "Recepție furnizor"
}'Răspuns 201 — rândul de stoc actualizat (cu produsul și locația):
{
"data": {
"id": 3391,
"product_id": 812,
"location_id": 14,
"quantity": 20,
"reserved_quantity": 0,
"batch_number": "B-2026-06",
"product": { "product_id": 812, "sku": "BRK-PAD-001", "name": "Plăcuțe frână față" },
"location": { "id": 14, "location_code": "CULOAR1-RAFT-3" }
}
}Scoatere stoc
Câmpuri pentru POST /api/v1/inventory/remove-stock:
| Câmp | Tip | Obligatoriu | Note |
|---|---|---|---|
product_id | integer | da | Trebuie să existe (products.product_id). |
location_id | integer | da | Trebuie să existe (locations.id). |
quantity | integer ≥ 1 | da | Cantitatea de scos. |
notes | text | nu | Notă liberă, salvată în mișcarea de stoc. |
Dacă produsul nu are stoc la acea locație sau cantitatea cerută depășește stocul disponibil, primești 422:
{ "error": "Insufficient stock." }Fără cod de motiv
Operațiunile de stoc nu primesc un cod de motiv de la apelant. Fiecare adăugare/scoatere se înregistrează automat în inventory_transactions (vizibil în pagina Mișcări stocuri) cu un motiv setat de server (Adăugare stoc via API / Eliminare stoc via API). Singurul text liber pe care îl poți atașa este notes.