Documentazione / Tecnica

Architettura e API.

Contratti implementati per backend Laravel, applicazione Flutter, multitenancy e funzionamento offline.

Stack e confini

ComponenteTecnologiaResponsabilità
BackendLaravel 12 / PHP 8.2REST API, autenticazione, pannello admin con shell premium e regole operative.
Database serverMySQLDati multi-tenant e transazioni di consegna/stock.
MobileFlutterApplicazione agente unica esposta come Consegne Panificio su Android e iOS, bundle it.appteam.panificio.
Storage localeSQLite + secure storageSnapshot, outbox e storico DDT offline per tenant; token custodito dal dispositivo.
StampaBluetooth LE / ESC/POSSelezione stampante dall’app e invio documento valorizzato alle termiche BLE, incluse PT210 generiche, senza rendering PDF o immagini.

Multitenancy SaaS

Il modello utilizza uno schema condiviso con chiave bakery_id. L’entità bakeries identifica l’ambiente; gli account piattaforma non appartengono a un panificio, mentre amministratori tenant e agenti sì.

Dominio datiIsolamento
Anagrafichecustomers, products, vans, agents filtrati per panificio.
Configurazionecustomer_product_prices, agent_customer, routes, route_stops appartengono al tenant.
Operativitàturns, van_inventories, orders, deliveries e righe includono bakery_id.
UnicitàCodici operativi, numeri ordine e DDT sono unici nel relativo panificio.
Ciclo di vitaIl platform admin può sospendere il tenant o eliminarlo; l’eliminazione rimuove dati operativi, utenti tenant e token mobile.

API mobile

Autenticazione agente

GET /api/v1/config espone la configurazione pubblica mobile, compreso lo stato OneSignal e l’eventuale App ID condiviso. POST /api/v1/auth/login accetta email, password e identificazione dispositivo. Risponde con token Sanctum, utente, panificio e identità push OneSignal dell’agente. Sono accettati soltanto agenti attivi associati a tenant attivi.

Snapshot mattutino

GET /api/v1/agent/sync?date=YYYY-MM-DD richiede Bearer token e restituisce il turno dell’agente, i clienti esplicitamente assegnati e il listino valido specifico per ciascuno. Le righe degli ordini pianificati includono sia la quantità ordinata sia l’eventuale reso pianificato.

{
  "bakery": {"id": 1, "name": "Forno Rossi", "slug": "forno-rossi", "vat_number": "IT12345678901", "address": "Via del Pane 1", "city": "Roma", "postal_code": "00100"},
  "agent": {"id": 7, "name": "Marco Alba"},
  "turn": {"route": {}, "van": {}, "ddt_numbering": {"next_sequence": 501}},
  "assigned_customers": [{"id": 14, "business_name": "Market Centro", "vat_number": "IT98765432109", "price_list": [{"product_id": 3, "unit_price": "1.2500"}]}],
  "stops": [{"customer": {}, "price_list": [], "orders": []}],
  "van_inventory": []
}

Outbox consegne

POST /api/v1/agent/deliveries/sync riceve massimo 100 bolle per richiesta. Ogni bolla usa un client_uuid idempotente e include le righe con sale_type planned oppure van_sale. Se l’ordine nasce nell’app, il server crea lo storico ordine in stato chiuso e lo collega al DDT.

La transazione server verifica appartenenza al tenant, assegnazione del cliente all’agente, ordine opzionale, intervallo DDT assegnato, listino valido e stock disponibile prima di inserire il documento e aggiornare quantità consegnate e rese. Ogni bolla è trattata separatamente: conflitti DDT, stock insufficiente, riferimenti scaduti o altre validazioni operative vengono restituiti in rejected con il relativo client_uuid, senza impedire la registrazione delle altre bolle valide del batch.

Storico DDT agente

GET /api/v1/agent/deliveries restituisce gli ultimi DDT emessi dai turni dell’agente autenticato, con cliente, righe, valori di consegnato/reso e data di eventuale stampa. La risposta è sempre filtrata per tenant e agente.

POST /api/v1/agent/deliveries/print-sync conferma in modo idempotente la prima stampa riuscita di un DDT già sincronizzato, coprendo anche la ristampa eseguita dopo un precedente errore della stampante.

Offline Flutter

  • Il token restituito dal login è memorizzato in flutter_secure_storage.
  • SQLite mantiene snapshot, consegne pendenti, conferme stampa pendenti e l’elenco ordini effettuati usando lo slug del panificio come scope locale.
  • L’area admin web usa una shell Blade condivisa con navigazione raggruppata per domini, header contestuali di pagina, card metriche e viste operative riorganizzate per dashboard, anagrafiche, turni, ordini e agenti.
  • Il premium pass 2 introduce ricerca server-side nelle anagrafiche clienti e nel listino cliente, oltre a una pagina dettaglio agente separata dalla lista per assegnazioni filtrabili e contesto operativo.
  • Il premium pass 3 aggiunge breadcrumb contestuali nelle viste figlie, modifica inline della scheda agente e ordini pianificati multi-riga con righe prodotto dinamiche e validazione listino per ogni riga.
  • Il premium pass 6 introduce un’area impostazioni tenant con modifica dell’intestazione del panificio attivo, anteprima operativa della testata e accesso diretto dalla navigazione admin.
  • Le anagrafiche clienti, prodotti, furgoni e giri usano una gestione tabellare compatta con ricerca, modifica rapida in riga, ordinamento più naturale per identità anagrafica e indicatori di completezza operativa in stile backoffice, per evitare eccessivo scroll su dataset estesi.
  • L’anagrafica clienti include ora il campo `email`, usato come destinatario dei tracciati Excel inviati dal backoffice quando un ordine ha già generato il relativo DDT.
  • L’area `admin/operations/orders` espone filtri GET per periodo consegna, cliente, stato operativo e ricerca libera su ordine, DDT, cliente o codice; la tabella filtrata alimenta la selezione singola o totale degli ordini esportabili, export singolo `.xls`, export TeamSystem testuale tramite `format=teamsystem`, export massivo `.zip`, invio email singolo e invio/rinvio email massivo dei tracciati cliente. Il file Excel è generato a partire dai `delivery_items` del DDT collegato e produce sempre due blocchi logici nello stesso foglio: prima le righe consegnate e poi le righe di reso con quantità e totale negativi. Il file TeamSystem usa record fixed-width da 400 caratteri: una riga `TESTA` per il documento e righe `CORPO` per consegnati e resi. Negli ZIP massivi i nomi file vengono resi univoci quando più DDT dello stesso cliente generano la stessa intestazione.
  • La configurazione OneSignal è centralizzata in `platform_settings`: il super admin salva App ID e REST API Key globali, mentre ogni tenant può solo riusare tale configurazione condivisa per l’invio ai propri agenti.
  • L’area `admin/settings/push-notifications` consente invio push a un singolo agente o a un set di agenti selezionati; il backend invia verso OneSignal usando alias `external_id` derivato dall’utente agente, include sempre un fallback `en` oltre al contenuto `it` richiesto dal provider, accetta sia un URL immagine opzionale sia un upload diretto dal backoffice e inoltra il media come `big_picture` Android e `ios_attachments`.
  • Il log push del panificio viene automaticamente potato alle ultime 50 righe dopo ogni nuovo invio, oltre al pulsante manuale di svuotamento completo; ogni riga puo conservare URL pubblico e path storage dell’immagine inviata, così i file uploadati vengono rimossi quando una riga esce dallo storico o quando il log viene svuotato.
  • La dashboard tenant usa una veduta selezionabile (`deliveries` o `products`) con filtri GET per intervallo date, cliente, agente, mezzo e giro; il rendering separa cabina di regia, metriche principali, controlli operativi, classifica clienti top e vista dati principale, ma raccoglie i filtri in un drawer espandibile per migliorare la resa responsive su schermi piccoli. L’export Excel riproduce la sola veduta attiva con gli stessi filtri applicati e, nella veduta `deliveries`, genera una riga per ogni articolo del DDT con quantità di reso esposta sempre in negativo.
  • L’app Flutter agente usa una home di giro con ricerca clienti, filtri rapidi per serviti/da servire/criticità, contatore dei clienti visibili, metriche compatte in griglia 2x2, contenuto inferiore scrollabile, priorità visive sulle tappe, badge sync più chiari, riepilogo del carico residuo del van con prodotti in esaurimento e un composer ordine con ricerca prodotti e riepilogo economico progressivo.
  • L’app Flutter inizializza OneSignal leggendo l’App ID dal backend, effettua `login` OneSignal con un `external_id` stabile dell’agente e lo rimuove al logout, così i push restano agganciati all’identità utente e non a un singolo token memorizzato lato server.
  • Lo storico notifiche agente è locale al dispositivo: il client intercetta le notifiche OneSignal in foreground o al click, le salva in SQLite con stato letto/non letto, immagine e payload essenziale, mostra il contatore in alto a destra e consente di rivederle anche dopo la chiusura della notifica di sistema.
  • Su Android l’app dichiara solo i permessi coerenti con le funzioni esposte: rete, notifiche push OneSignal, Bluetooth LE per la stampante e compatibilità legacy con localizzazione limitata ad Android 11 e precedenti per la scansione BLE. Su iOS espone la descrizione privacy Bluetooth per collegare la stampante termica e stampare DDT e documenti di consegna.
  • La release Android usa ora un keystore dedicato letto da `android/key.properties`; le credenziali restano in file privati esclusi dal versionamento. La cartella iOS include anche il `Podfile` standard e il target `OneSignalNotificationServiceExtension` per agevolare il lavoro successivo su Xcode e CocoaPods e abilitare il rich media delle push.
  • Per le immagini push il progetto usa il disco Laravel `public`, ma su questo hosting il path statico `/storage/...` puo essere intercettato dal symlink pubblico prima di Laravel. Per questo i nuovi upload usano l'URL canonico `/push-media/{file}` servito da route controllata, mentre gli eventuali link legacy `/storage/push-notifications/...` vengono riscritti internamente verso la stessa route senza esporre l'intera cartella storage.
  • Check operativo deploy/restore: se un'immagine push restituisce `403`, verificare prima di tutto che `public/storage`, `storage/app/public` e i file in `storage/app/public/push-notifications/` non risultino con owner/gruppo errati dopo operazioni eseguite come `root`. In questo hosting devono restare servibili da `appteamit:appteamit`.
  • Un logout azzera la sessione; l’accesso successivo non legge dati offline di un tenant differente.
  • Il servizio di sincronizzazione scarica lo snapshot e invia l’outbox quando la connettività è disponibile durante le azioni operative dell’utente, come sincronizzazione manuale, chiusura ordine o ristampa; inoltre la schermata giro ascolta il ritorno rete e tenta automaticamente il flush delle consegne e delle conferme stampa pendenti. Gli errori applicativi vengono mostrati con il messaggio restituito dalle API e non più come solo codice HTTP.
  • Il composer ordine mobile espone solo clienti assegnati e prodotti del rispettivo listino; prima della chiusura raccoglie quantità consegnata e reso.
  • Il composer usa anche l’inventario del furgone ricevuto nello snapshot e le bolle ancora pendenti sul dispositivo per bloccare quantità superiori al residuo disponibile per prodotto.
  • La stampante scelta viene memorizzata nello storage sicuro tramite identificatore Bluetooth del dispositivo.
  • Il servizio BLE esegue scansione e connessione, privilegia le characteristic di stampa comunemente usate dalle termiche generiche e invia ESC/POS in chunk da massimo 20 byte con pausa breve per evitare perdite silenziose di dati sulla PT210.
  • La chiusura ordine memorizza e tenta di stampare il DDT prima dell’upload; la mancanza di rete non blocca la ricevuta e lo storico indica gli elementi ancora da sincronizzare.
  • La sequenza DDT successiva è calcolata dal massimo numero già registrato sul server e dai documenti conservati localmente, per non riutilizzare un numero dopo il riavvio dell’app.
  • Il documento DDT riporta numero progressivo assegnato dal blocco del turno, data ordine, intestazioni di panificio e cliente, righe valorizzate, totali consegnato/reso/netto e spazio firma/timbro; l’ultimo ordine può essere ristampato.
  • Le righe consegnato/reso e i totali sono formattati in massimo 32 caratteri per riga, compatibili con la stampa stretta della PT210.
  • Le schermate operative usano gli inset di sistema in modo coerente su login, home giro, notifiche, composer ordine e storico ordini, evitando che liste, stati vuoti, loading o pulsanti di chiusura ordine vengano coperti dalla barra di stato o di navigazione Android.

Sicurezza operativa

  • L’area amministrativa usa sessione Laravel e middleware distinti per amministratore piattaforma e tenant.
  • Un tenant con stato sospeso non può essere utilizzato né dal backoffice del panificio né dalle API mobile, inclusi token già emessi.
  • L’API rifiuta una consegna per un cliente non presente nell’assegnazione dell’agente autenticato.
  • Le API app sono protette da Sanctum; il login emette un token dedicato con abilità di sincronizzazione agente.
  • Il document root protegge file applicativi e configurazioni server tramite regole Apache.
  • Questa documentazione pubblica non contiene password né valori di configurazione riservati.