API Prodotti e Categorie
Panoramica
La piattaforma espone due servizi per la gestione del catalogo: products (Universal Item Model) e categories (categorie gerarchiche). Entrambi usano TenantDbMixin per l'isolamento multi-tenant e SyncMixin per la sincronizzazione delta con i client Flutter.
Base URL Prodotti: /api/products
Base URL Categorie: /api/categories
Prodotti: Endpoint CRUD
Elenco prodotti con paginazione
curl -X GET "https://api.example.com/api/products?page=1&pageSize=50&categoryId=pizze&active=true" \
-H "Authorization: Bearer <JWT_TOKEN>"
Parametri query: page, pageSize, categoryId, active, type (RAW_MATERIAL, DISH, MERCHANDISE, SERVICE, KIT), search.
La risposta include paginazione completa:
{
"items": [{ "_id": "...", "sku": "MARG01", "name": "Margherita", "price": 8.50, "categoryId": "pizze" }],
"pagination": { "page": 1, "pageSize": 50, "total": 127, "totalPages": 3 }
}
Ricerca rapida (ottimizzata per POS)
curl -X GET "https://api.example.com/api/products/search?q=margh" \
-H "Authorization: Bearer <JWT_TOKEN>"
Cerca per nome e SKU con regex case-insensitive. Limite massimo: 20 risultati.
Creazione prodotto
curl -X POST "https://api.example.com/api/products" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"sku": "MARG01",
"name": "Pizza Margherita",
"price": 8.50,
"categoryId": "pizze",
"type": "DISH",
"vatRate": 10,
"isSaleable": true,
"isStockable": true,
"active": true
}'
Aggiornamento e cancellazione
# Aggiorna prodotto
curl -X PUT "https://api.example.com/api/products/<ID>" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "price": 9.00, "description": "Pomodoro, mozzarella, basilico" }'
# Elimina prodotto (soft delete)
curl -X DELETE "https://api.example.com/api/products/<ID>" \
-H "Authorization: Bearer <JWT_TOKEN>"
Lookup per barcode
curl -X GET "https://api.example.com/api/products/lookup-barcode/8001234567890" \
-H "Authorization: Bearer <JWT_TOKEN>"
Universal Item Model
Il modello prodotto supporta diversi pattern tramite flag componibili:
| Flag | Significato |
|---|---|
isSaleable | Vendibile al POS |
isStockable | Gestito a magazzino |
isPurchasable | Acquistabile da fornitori |
hasRecipe | Ha distinta base/ricetta |
isModifier | Modificatore (es. "extra mozzarella") |
Dati strutturati inclusi: salesData, inventoryData, purchaseData, recipeData, variantGroups, costData, allergens.
Categorie Gerarchiche: Materialized Path
Il servizio categories implementa il pattern Materialized Path per gestire alberi di categorie con query efficienti.
Struttura dati
Ogni categoria contiene:
categoryId: slug univoco (es.birre_artigianali)path: percorso completo (es./bevande/birre/birre_artigianali)level: profondita nell'albero (0 = radice)parentId: riferimento alla categoria padre
Creazione categoria
curl -X POST "https://api.example.com/api/categories" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Birre Artigianali",
"parentId": "birre",
"displayInMenu": true,
"order": 2
}'
Il categoryId viene generato automaticamente dal nome (slug) e path/level vengono calcolati in base al parent.
Albero completo
curl -X GET "https://api.example.com/api/categories/tree" \
-H "Authorization: Bearer <JWT_TOKEN>"
Ritorna la struttura annidata con children per ogni nodo.
Filtraggio prodotti per gerarchia
Quando si filtra per categoryId, il servizio products.list espande automaticamente la query a tutti i discendenti:
// Internamente, se "bevande" ha figli ["birre", "vini", "cocktails"]:
query.categoryId = { $in: ["bevande", "birre", "vini", "cocktails"] }
Questo significa che filtrare per una categoria padre restituisce anche i prodotti delle sotto-categorie.
Spostamento categoria
curl -X POST "https://api.example.com/api/categories/move" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "categoryId": "birre_artigianali", "newParentId": "birre_speciali" }'
Lo spostamento aggiorna path e level della categoria e di tutti i suoi discendenti tramite regex replace.
Sincronizzazione Delta
Entrambi i servizi espongono un endpoint syncDown per i client Flutter:
curl -X GET "https://api.example.com/api/products/sync?lastSync=2026-03-17T08:00:00Z" \
-H "Authorization: Bearer <JWT_TOKEN>"
Ritorna solo i record modificati dopo lastSync, con campi mappati per compatibilita Isar (serverId, lastUpdated, synced).
FAQ
D: Come funziona la cache dei prodotti?
R: Il servizio usa Redis cache con chiavi basate su tenantId, categoryId, active, page, pageSize. La cache viene invalidata automaticamente dopo create/update/delete tramite hook after.
D: Posso importare prodotti in batch?
R: Si, l'endpoint POST /products/batch accetta un array di prodotti e esegue upsert per ciascuno, utile per la sincronizzazione iniziale dal POS.
D: Come gestisco le varianti di un prodotto?
R: I prodotti di tipo food usano variantGroups (gruppi dinamici, es. dimensione pizza). I prodotti retail usano variantMatrix con dimensioni statiche (taglia, colore).
Vedi Anche
- API Ordini -- Creazione ordini con riferimenti ai prodotti
- Panoramica Architettura -- Visione d'insieme della piattaforma
- Setup Sviluppo Locale -- Configurazione ambiente di sviluppo