Skip to main content

🍷 Wines — panoramica

La feature Wines consente a una company (Cantina/Winery) di:

  • elencare e filtrare vini/annate,
  • creare un Wine e una relativa Vintage,
  • aggiungere una nuova Vintage a un Wine esistente,
  • modificare Wine/Vintage,
  • consultare il dettaglio di una Vintage.

Accesso: in Dashboard, la tab Wines è abilitata per company di tipo WINERY con ruoli adeguati (es. Admin / WineManager).


🧭 Routing e moduli

// src/dashboards/wine/wine.module.ts
@NgModule({
declarations: [WineComponent],
imports: [
SharedModule, CommonModule,
RouterModule.forChild([
{
path: '',
component: WineComponent,
children: [
{ path: '', loadChildren: () => import('./wine-home-page/wine-home-page.module').then(m => m.WineHomePageModule) },
{ path: 'create', loadChildren: () => import('./wine-create/wine-create.module').then(m => m.WineCreateModule) },
{ path: ':vintageId', loadChildren: () => import('./wine-detail-page/wine-detail-page.module').then(m => m.WineDetailPageModule) },
]
}
])
]
})
export class WineModule {}
// src/dashboards/wine/wine-detail-page/wine-detail-page.module.ts
@NgModule({
declarations: [WineDetailPageComponent],
imports: [
SharedModule, CommonModule,
RouterModule.forChild([
{ path: '', component: WineDetailPageComponent },
{ path: 'edit', loadChildren: () => import('./wine-edit-page/wine-edit-page.module').then(m => m.WineEditPageModule) },
{ path: 'create-vintage', loadChildren: () => import('./wine-add-vintage/wine-add-vintage.module').then(m => m.WineAddVintageModule) },
])
]
})
export class WineDetailPageModule {}

Mappa rotte principali

  • /dashboard/winesHomePage (lista + stats)
  • /dashboard/wines/createCreazione Wine + Vintage
  • /dashboard/wines/:vintageIdDettaglio Vintage
    • /editModifica Wine/Vintage
    • /create-vintageAggiungi Vintage a Wine esistente

🏗️ Componenti e responsabilità

WineComponent (shell)

  • Semplice contenitore con <router-outlet>.
  • Tutta la logica vive nelle pagine figlie.

WineHomePageComponent (lista + statistiche)

  • UI:
    • KPI cards: #Wines, #Vintages, #eLabels.
    • Search con debounce (300ms).
    • Tabella con colonne: wine (immagine + nome vintage), updatedAt (data), score (media rating), azione “apri dettaglio”.
    • Paginator (pagine e records per page via AlbiOption).
  • Store:
    • USER_ACCES_SELECTORS.selectSelectedUserAccesses e selectSelectedEnterpriseAccess → risolve wineryId.
  • Dati/Backend:
    • Lista/pagine: GET wine/wines/vintages?winery_id={...}&page={n}&limit={k}&q={search?}
    • Conteggi:
      • GET wine/wines?winery_id={...} → #Wines
      • GET wine/wines/vintages?winery_id={...} → #Vintages
      • GET elabels → #eLabels
  • Pipes & helpers:
    • resolveVintageName, resolveDateStringPipe, imageUrlWithLastUpdate.
  • Navigazione:
    • “Nuovo” → /dashboard/wines/create
    • “Apri” (riga) → /dashboard/wines/:vintageId
  • Stati UI:
    • isLoading$, isTableDataLoading$, getErrorMessage$, selectedPage$, recordPerPageControl.

WineDetailPageComponent (dettaglio Vintage)

  • Fetch:
    • GET wine/wines/vintages/:vintageId
    • poi GET wine/wines/:wineId collegato.
  • UI:
    • Header con back.
    • Box informazioni Wine (colore, tipologie, regione, ecc.) e Vintage (nome, descrizioni localizzate, immagini).
    • Azioni:
      • Edit/dashboard/wines/:vintageId/edit
      • Add Vintage/dashboard/wines/:vintageId/create-vintage
  • Note:
    • Le descrizioni vengono risolte con utilità/localizzazione (TranslateService + dizionari).

WineCreateComponent (crea Wine + Vintage)

  • Form:
    • wineCreateForm (Wine): nome, colore, tipologie, regione/paese, uve (chips), ecc.
    • vintageCreateForm (Vintage): anno, descrizioni, pairing, allergeni, immagini (label/bottle), ecc.
  • Flusso salvataggio:
    1. POST wine/wines → crea Wine (status: active, wineryId).
    2. POST wine/wines/{wineId}/vintages → crea Vintage (mapping: chips/list → array, testi, year, ecc.).
    3. (Opzionale) Upload immagini:
      • POST wine/wines/{wineId}/vintages/{vintageId}/bottle (binario, header content-type)
      • POST wine/wines/{wineId}/vintages/{vintageId}/label (binario, header content-type)
    4. Messaggio successo e redirect/aggiornamento.
  • Rollback in caso di errore (coerenza dati):
    • Se fallisce la creazione Vintage, recupera ETag con GET (full response) wine/wines/{wineId}?raw=true e fa DELETE wine/wines/{wineId} con ETag per ripulire il Wine appena creato.

WineEditPageComponent (modifica Wine/Vintage)

  • Init:
    • GET (full response) wine/wines/vintages/:vintageId?raw=trueETag vintage
    • GET (full response) wine/wines/:wineId?raw=trueETag wine
    • Prepopola wineCreationForm e vintageCreationForm.
    • Campi year e simili della Vintage sono disabilitati (immutabili).
  • Salvataggio:
    • PATCH con ETag (concorrenza ottimistica):
      • PATCH wine/wines/{wineId} (Wine)
      • PATCH wine/wines/{wineId}/vintages/{vintageId} (Vintage)
    • Upload immagini come in creazione:
      • POST .../label e/o POST .../bottle (binario + header content-type).
  • Mapping UI → API (esempi):
    • Grapes: chips AlbiChipType[] → struttura richiesta dal BE.
    • Pairings: primaryFoods (match perfect) + secondaryFoods (match good).
    • Descriptions: array localizzato { locale, text }.
  • UI:
    • Barra “Save” fissa visibile quando i form sono dirty.

WineAddVintageComponent (aggiungi Vintage a Wine esistente)

  • UI:
    • Mostra wine-creation-form in read-only (Wine non editabile qui).
    • vintage-creation-form per la nuova annata.
  • Flusso:
    1. POST wine/wines/{wineId}/vintages con mapping campi (pairings, allergens, philosophy, awards, moods, descriptions, …).
    2. (Opzionale) Upload bottle/label come in creazione.
    3. Messaggio successo e redirect (es. al dettaglio).

🔄 Sequenze principali (mermaid)

A) Lista & statistiche

sequenceDiagram
autonumber
participant Store as NgRx Store
participant Home as WineHomePage
participant BE as BackendService

Home->>Store: read selectedUserAccess + selectedEnterpriseAccess
Home->>Home: resolve wineryId
Home->>BE: GET /wine/wines/vintages?winery_id=&page=&limit=&q=
BE-->>Home: { vintages, pages, page }
Home->>BE: GET /wine/wines?winery_id=
BE-->>Home: { wines }
Home->>BE: GET /elabels
BE-->>Home: { elabels }
Home-->>Home: update KPI + table + paginator

B) Crea Wine + Vintage (con immagini e rollback)

flowchart TD
A[Compila Wine + Vintage] --> B[POST /wine/wines]
B -->|wineId| C[POST /wine/wines/{wineId}/vintages]
C -->|vintageId| D{Immagini presenti?}
D -- sì --> E1[POST .../{vintageId}/bottle]
E1 --> E2[POST .../{vintageId}/label]
D -- no --> F[Mostra success + redirect]
E2 --> F[Mostra success + redirect]
C -- errore --> R[GET full /wine/wines/{wineId}?raw + DELETE con ETag]

C) Edit Wine/Vintage (ETag)

sequenceDiagram
autonumber
participant Edit as WineEditPage
participant BE as BackendService

Edit->>BE: GET full /vintages/:vintageId?raw
BE-->>Edit: vintage + ETag_vintage
Edit->>BE: GET full /wines/:wineId?raw
BE-->>Edit: wine + ETag_wine
Edit-->>Edit: form patch + mapping
Edit->>BE: PATCH /wines/:wineId (If-Match: ETag_wine)
Edit->>BE: PATCH /wines/:wineId/vintages/:id (If-Match: ETag_vintage)
Edit->>BE: POST label/bottle (binari + content-type)
BE-->>Edit: 200 OK
Edit-->>Edit: success + toast + redirect

🧩 Dipendenze UI/tecniche

  • albi-ui: albi-table, albi-paginator, albi-autocomplete, albi-button, albi-icon, albi-loader, ecc.
  • Shared components:
    • wine-creation-form (gestione form del Wine)
    • vintage-creation-form (gestione form della Vintage)
  • Pipes: resolveVintageName, resolveDateStringPipe, imageUrlWithLastUpdate.
  • NgRx Store: USER_ACCES_SELECTORS per context di company/permessi.
  • TranslateService: etichette/titoli/dizionari (IT/EN).
  • BackendService: CRUD + upload binari + ETag flow (get full response, patch, delete).

⚠️ Note operative & edge cases

  • Concorrenza: in edit si usa ETag (If-Match) per evitare write conflict su Wine/Vintage.
  • Rollback creazione: se fallisce la creazione della Vintage, si cancella il Wine appena creato (consistenza).
  • Campi immutabili: anno/yearType della Vintage non modificabili in edit (disabilitati).
  • Mapping UI → API:
    • Chips (grapes, foods, allergens, moods, philosophy, awards) vanno normalizzati come richiesto dal BE.
    • Descrizioni localizzate { locale, text }.
  • Ricerca: input con debounce 300ms, compone q nella query delle vintages.
  • UX:
    • “Save bar” visibile solo quando i form sono dirty.
    • Placeholder immagini e tooltip nome vintage in tabella.
    • Pannello KPI sempre coerente con il wineryId attivo.

🧭 Estendere la feature

  • Nuovi campi Wine/Vintage:
    1. estendere i FormGroup nei componenti di creazione/edit,
    2. aggiornare il mapping nel body di POST/PATCH,
    3. gestire l’eventuale upload (binari + content-type),
    4. aggiungere le traduzioni necessarie.
  • Nuove colonne tabella: aggiornare wineTableColumns + template (ngSwitch) e, se serve, l’endpoint di lista (se richiede più dati).
  • Filtri avanzati: introdurre nuovi controlli nel HomePage e serializzare in query (winery_id, q, range date, ecc.).