Skip to main content

🍷 Wine-List — panoramica

La feature Wine-List consente di creare, gestire e pubblicare le liste vini di una location aziendale, con:

  • selezione di vini/annate (anche user vintages);
  • definizione dei modelli di vendita (calice/bottiglia/caraffa + capacità) e prezzi;
  • opzioni di template e sezioni/extra informazioni da mostrare;
  • attivazione/disattivazione di una lista per la location;
  • QR code per accesso Winelist e AI Sommelier (se sottoscritti);
  • gestione del menu piatti associato (categorie e voci).

L’intera feature è lazy sotto dashboard/wine-list e usa un piccolo servizio locale per ricordare la location selezionata.


🧭 Routing & Moduli

// wine-list.module.ts (semplificato)
const WINE_LIST_ROUTES: Route[] = [
{
path: '',
component: WineListComponent, // shell con <router-outlet>
children: [
{ path: '', loadChildren: () => import('./wine-list-homepage/wine-list-homepage.module').then(m => m.WineListHomepageModule) },
{ path: 'create', loadChildren: () => import('./create-wine-list/create-wine-list.module').then(m => m.CreateWineListModule) },
{ path: 'menu/:id', loadChildren: () => import('./edit-list-menu/edit-list-menu.module').then(m => m.EditListMenuModule) },
{ path: ':id', loadChildren: () => import('./edit-wine-list/edit-wine-list.module').then(m => m.EditWineListModule) },
]
}
];
  • WineListComponent: shell che resetta la location salvata al ngOnDestroy.
  • WineListHomepage: indice per location, QR, attiva/disattiva lista, tabella liste.
  • CreateWineList/EditWineList: pagina di dettaglio/modifica riusata tramite <wine-list-detail-component>.
  • EditListMenu: gestione menu piatti per la lista (/menu/:id).

🧠 Contesto location (persistenza locale)

// wineListLocation.service.ts
class WineListLocationService {
private _selectedLocation?: AlbiOption;
getSelectedLocation(): AlbiOption { return this._selectedLocation; }
setSelectedLocation(location?: AlbiOption) {
localStorage.setItem('wineListSelectedLocation', JSON.stringify(location));
this._selectedLocation = location;
}
}
  • La location si sceglie da dropdown in homepage; viene persistita in localStorage.
  • Se l’utente è vincolato a una location nel suo access, il dropdown viene pre-selezionato e disabilitato.

🏠 Homepage

Dati & stato

  • companyAddresses$ : AlbiOption[] → locations ottenute via CompanyLocationService.
  • locationSelectorControl : FormControl<AlbiOption> → al change:
    • GET winelists (header: useCompanyHeader, specificLocationId).
    • Aggiorna wineLists$ e lista attiva.
    • Costruisce QR options per:
      • Winelisthttps://winelist{[-stage]}.albicchiere.com/<locationId>
      • Sommelierhttps://sommelier{[-stage]}.albicchiere.com/<locationId> (se sottoscrizione ai_sommelier attiva nella company).
  • isAiSommelierPlanActive$ → da selectedCompany.subscriptions.

Azioni

  • Crea nuova → nav dashboard/wine-list/create.
  • Modifica una lista → nav dashboard/wine-list/:id.
  • Vai al menu della lista attiva → nav dashboard/wine-list/menu/:id.
  • Attiva/Disattiva lista:
    • POST winelists/:id/enable / POST winelists/:id/disable con header location.
  • Elimina lista:
    • DELETE winelists/:id con conferma modale.
  • Scarica QR: serializza l’SVG generato da ngx-qrcode-styling e scarica image.svg.

✍️ Create/Edit: <wine-list-detail-component>

Componente unico per creazione nuova e modifica esistente:

  • in Create (route /create): usato direttamente dal wrapper CreateWineListComponent;
  • in Edit (route /:id): EditWineListComponent passa @Input() wineListId.

Stato principale

  • Selezione vini: selectedWines$ : { vintage? | userVintage?, isSelected, isVisible, modelsPrices[] }[]
  • Risultati catalogo: catalogResults$ (ricerca con debounce); vista results/selected.
  • Template: selectedTemplate$ : 'default1' | ....
  • Set modelli: wineModelSet$ : Set<'glass|' | 'bottle|750' | 'jug|1000' | ...>
  • Colonne prezzi: selectedWineTable$ costruito da wineModelSet$
    (di default: calice + bottiglia 750)
  • Extra info (switch): description, grapes, philosophy, alcohol, foods.
  • Sezioni (switch): albicchiereSection, lowAlcoholSection.
  • Form di supporto:
    • Aggiungi Modello: modelName (glass|bottle|jug) + capacity (ml, opzionale).
    • Aggiungi Vintage non presente (crea user_vintage).

Ricerca & aggiunta vini

  • Catalogo vini/annate (server) con debounce → popola catalogResults$.
  • Se non si trova, crea UserVintage:
    POST wine/user_vintages
    body: {
    name, wineryId, wineId, year, wineColor, temperature?, alcohol?
    }
    headers: { useCompanyHeader: true, specificLocationId }
    → aggiunge la userVintage ai selezionati.
  • Le vintages mostrate includono badge “selezionato” e toggle “mostra tutte le annate”.

Modelli & prezzi

  • Aggiungi modello → aggiorna wineModelSet$ e quindi le colonne della tabella prezzi (glass|, bottle|750, jug|…).
  • Ogni riga (vintage selezionata) espone FormControl per ogni modello:
    • validazione: numeri > 0; gruppi invalidi evidenziati (dot rosso, nome evidenziato).
  • Il mapping per payload genera per ciascuna vintage:
    modelPrices: ModelPrices[] = [
    { modelName: 'glass', capacity: undefined, price: number },
    { modelName: 'bottle', capacity: 750, price: number },
    { modelName: 'jug', capacity: 1000, price: number },
    ...
    ]

Salvataggio (ETag + preview)

  • Edit (esiste wineListId):
    PATCH winelists/:id  // header: If-Match: <ETag>
    body: {
    name, template,
    vintages: Array<{ vintageId | userVintageId, modelPrices[] }>,
    extraWineData: string[], // es. ['description','grapes',...]
    categories: string[], // es. ['albicchiereSection','lowAlcoholSection']
    }
    headers: { useCompanyHeader: true, specificLocationId }
  • Create:
    POST winelists  // body come sopra
  • Azioni post-save:
    • View: genera preview token e apre nuova tab:
      GET  winelists/:id/preview_token
      open https://winelist{[-stage]}.albicchiere.com/preview/:id?token=...
    • Close: ritorna alla homepage.

🧾 Edit List Menu (/menu/:id)

Pagina per gestire il menu piatti della Wine-List (categorie predefinite): 'appetizers' | 'first_course' | 'second_course' | 'pizza' | 'sides' | 'desserts'.

Stato & forme

  • winelistData : signal<IWineList> + winelistEtag : signal<string> (ottenuto via getFullResponse).
  • dishForm : FormGroupname (req), description, price (req, >0), category (req).
  • Pipe filterListMenu per filtrare voci per categoria.

Operazioni (tutte con header location + If-Match ETag in PATCH):

  • Aggiungi/Modifica piatto: aggiorna array menu e
    PATCH winelists/:id  { menu }
  • Rimuovi piatto: conferma modale → aggiorna menuPATCH.
  • Back: navigate(['dashboard/wine-list']).

🔄 Flussi principali (Mermaid)

1) Home: selezione location, QR e liste

sequenceDiagram
autonumber
participant UI as Homepage
participant Loc as CompanyLocationService
participant Svc as BackendService

UI->>Loc: getCompanyLocations(companyId, role)
Loc-->>UI: locations[]
UI->>UI: set dropdown (persist via WineListLocationService)
UI->>Svc: GET /winelists (specificLocationId)
Svc-->>UI: winelists[]
UI-->>UI: set activeList + build QR options (sommelier/winelist)

2) Create/Edit: selezione, modelli, prezzi, salvataggio

flowchart TD
A[Apri Create/Edit] --> B[Seleziona da catalogo]
B -->|not found| C[Create user_vintage (POST)]
B -->|trovato| D[Aggiungi alla selezione]
D --> E[Aggiungi Modello (glass/bottle/jug + capacità)]
E --> F[Compila prezzi per modello]
F --> G[Salva (POST/PATCH + If-Match ETag)]
G --> H{Action}
H -- "view" --> I[GET preview_token + open preview URL]
H -- "close" --> L[Ritorno homepage]

3) Menu: CRUD piatti

sequenceDiagram
autonumber
participant UI as EditListMenu
participant Svc as BackendService

UI->>Svc: GET (full) /winelists/:id // per ETag
Svc-->>UI: winelist + ETag
UI-->>UI: add/edit/remove in menu[]
UI->>Svc: PATCH /winelists/:id { menu } (If-Match: ETag)
Svc-->>UI: 200 OK + nuovo ETag

🔗 Dipendenze & integrazioni

  • albi-ui: tabelle, input, switch, dialog, tag, icone, autocomplete, paginator.
  • ngx-qrcode-styling: generazione e download QR (serializzazione SVG).
  • CompanyLocationService: elenco location filtrate per access/ruolo.
  • WineListLocationService: memoria locale della location selezionata.
  • BackendService: CRUD winelists, wine/user_vintages, attiva/disattiva, token preview.
  • Stripe (ngx-stripe): apertura customer portal (gestione piano AI Sommelier) dalla homepage.
  • TranslateService: testi/titoli localizzati.

✅ Note operative per chi subentra

  • Location-first: tutte le chiamate a winelists richiedono l’header specificLocationId.
  • La tabella prezzi è guidata dai modelli in wineModelSet$ → non duplicare colonne manualmente.
  • In Edit usare sempre ETag (If-Match) sia per la lista che per il menu.
  • Quando si crea un user_vintage, assicurarsi di passarne gli ID coerenti (wineryId, wineId) e attributi minimi (colore, anno).
  • Per aggiungere nuovi modelli (es. bottle 375):
    1. abilita il dialog Add Model con modelName='bottle' e capacity=375;
    2. viene automaticamente aggiunta la colonna e i FormControl nelle righe selezionate;
    3. il payload includerà il nuovo ModelPrices.
  • Le sezioni e le extra info sono liste di stringhe nel payload: aggiorna i dizionari di traduzione se cambi nomenclature.
flowchart LR
subgraph Context
SvcLoc[CompanyLocationService] --> Opts[Location options]
Opts --> Persist[WineListLocationService]
end
Persist --> Home[Homepage]
Home --> CreateEdit[Detail Component]
Home --> Menu[Edit List Menu]
CreateEdit --> Preview[Preview token flow]