Validarea deținerii — workshop-uri și add-on-uri
Un participant poate reveni la pasul de selecție a workshop-urilor/add-on-urilor de mai multe ori (de exemplu la schimbarea abonamentului). Pluginul trebuie să se asigure că workshop-urile la care participantul e deja înscris și add-on-urile deja cumpărate rămân dezactivate în formular și nu ajung, prin nicio cale, în coș a doua oară.
Problema identificată și rezolvată
La pasul de selecție, cardurile de workshop/add-on deja deținute sunt afișate dezactivate (disabled) — corect din punct de vedere vizual. Însă trei din cele patru layout-uri de card pentru workshop-uri (modal v2, panou dual/dp, orar/tt) și toate cele patru layout-uri pentru add-on-uri mai adăugau, în plus, un <input type="hidden"> necondiționat cu același name="selected_workshops[]" / selected_addons[]. Rolul acestui input ascuns era să „forțeze" trimiterea ID-ului chiar dacă checkbox-ul dezactivat nu e trimis de browser — util pentru add-on-urile incluse gratuit cu pass-ul, dar aplicat din greșeală și pentru elementele deja deținute.
Rezultat: la fiecare trimitere a formularului (inclusiv la „Înapoi"), ID-urile workshop-urilor/add-on-urilor deja deținute erau retrimise către handler-ul de checkout, care încerca să le adauge în coș.
Stratul de validare de la nivel de coș (dentisity_validate_cart_product(), agățat pe woocommerce_add_to_cart_validation și re-rulat pe woocommerce_check_cart_items) bloca deja aceste adăugări. Niciun participant nu a putut plăti a doua oară pentru același workshop sau add-on. Problema reală era experiența de utilizator: aceste încercări blocate generau notificări de eroare confuze („Ești deja înscris la acest workshop.", „Ai cumpărat deja acest add-on.") pe pagina de checkout, pentru acțiuni pe care participantul nu le inițiase.
A doua cauză, mai vizibilă pentru participant, era în calculul totalului afișat pe ecran în timpul selecției: checkbox-ul de add-on (.ao-hidden-checkbox) era bifat (checked) automat nu doar pentru add-on-urile gratuite incluse cu pass-ul, ci și pentru cele deja deținute — și purta data-price cu prețul real, nu 0. Funcția JS computeTotals() însumează toate checkbox-urile .ao-hidden-checkbox:checked, indiferent dacă sunt disabled, deci totalul afișat și eticheta „N add-on-uri selectate" includeau, în mod vizibil, prețul unor add-on-uri deja cumpărate — exact senzația de „a fost adăugat în coș" resimțită la apăsarea „Continuă la plată", chiar dacă coșul WooCommerce real nu conținea niciodată acel element a doua oară.
Fix aplicat
- Input-ul ascuns „owned" a fost eliminat complet din cele 3 layout-uri de workshop afectate — un workshop deținut se comportă acum la fel ca în lista simplă (
reg_days): doar dezactivat, fără retrimitere. - Condiția input-ului ascuns pentru add-on-uri a fost restrânsă să se aplice doar add-on-urilor incluse gratuit cu pass-ul (
$_ao_is_free), nu și celor pur și simplu deja cumpărate ($_ao_owned). - Bifarea automată (
$_ao_precheck) a checkbox-ului de add-on a fost, la rândul ei, restrânsă la același criteriu — doar$_ao_is_free. Un add-on deja deținut apare acum nebifat și dezactivat, la fel ca un workshop deja deținut, deci nu mai contribuie la totalul sau la numărătoarea calculate decomputeTotals(). - În bucla de adăugare în coș din handler-ul de checkout a fost adăugată o verificare explicită de deținere (workshop-uri prin
dentisity_get_user_workshop_group_ids(), add-on-uri prindentisity_user_addon_order_id()), astfel încât orice ID retrimis din greșeală este ignorat silențios, în loc să declanșezeadd_to_cart()și o notificare de eroare.
Tabel comparativ al condițiilor
Pentru fiecare condiție de eligibilitate, tabelul arată unde este verificată: la pasul de selecție din formularul de înregistrare, la adăugarea efectivă în coș (woocommerce_add_to_cart_validation), și la re-validarea din checkout (woocommerce_check_cart_items).
| Condiție | Pasul de selecție (formular înregistrare) | Coș (add_to_cart) | Checkout (woocommerce_check_cart_items) |
|---|---|---|---|
| Cont aprobat (identity check) | Pasul e vizibil doar după aprobare | Workshop: identity_check_status === 'approved' — dentisity_validate_cart_product() | Stare cont re-verificată general (email neconfirmat / în așteptare / respins / informații necesare) |
| Pass activ deținut (pentru workshop-uri) | Pass-ul e adăugat primul, în aceeași cerere | dentisity_user_pass_product_id() sau pass în coș | Workshop-urile sunt eliminate din coș dacă nu există niciun pass deținut/în coș |
| Pass activ deținut (pentru add-on-uri) | La fel | Aceeași regulă ca la workshop-uri | Add-on-urile sunt eliminate din coș dacă nu există niciun pass |
| Deja înscris la workshop | dentisity_get_user_workshop_group_ids($uid) dezactivează checkbox-ul (is_already_enrolled) | in_array($product_id, $enrolled) blochează adăugarea + notificare | Re-validat pe fiecare linie din coș prin aceeași funcție; acum filtrat și proactiv înainte de add_to_cart() în handler-ul de checkout |
| Workshop cumpărabil | dentisity_workshop_purchasable === 'yes' controlează is_unavailable | Aceeași verificare pe metadată | Eliminat din coș + notificare dacă devine necumpărabil după ce a fost adăugat |
| Workshop epuizat (stoc) | slots_left <= 0 → is_soldout | (nu e re-verificat explicit aici — se bazează pe gestiunea de stoc WooCommerce) | (nu există re-verificare explicită per linie — se bazează pe WooCommerce) |
| Workshop permis pentru acest pass (mod „specific") | Lista de workshop-uri permise filtrează ce e vizibil | (nu e verificat în dentisity_validate_cart_product — doar în bucla de adăugare din handler-ul de checkout) | (nu e re-validat per linie de coș — vezi nota de mai jos) |
| Add-on deja cumpărat | dentisity_user_addon_order_id($uid, $id) → dezactivează + bifează implicit | Blochează adăugarea + notificare | Re-validat prin aceeași funcție; acum filtrat și proactiv în handler-ul de checkout |
| Add-on cumpărabil | dentisity_addon_purchasable === 'yes' (ignorat dacă e gratuit cu pass-ul) | Aceeași regulă, ignorată pentru add-on-uri gratuite cu pass-ul | (nu există re-verificare specifică de „cumpărabil" în hook-ul de eliminare din coș — doar cerința de pass, mai sus) |
| Add-on epuizat (stoc) | _stock <= 0 → disabled | (nu e re-verificat explicit — se bazează pe stocul WooCommerce) | (aceeași lipsă ca la workshop-uri) |
| Add-on permis pentru tip participant / specializare / pass | Filtrează vizibilitatea în lista de add-on-uri afișată | Verificare completă, ignorată dacă add-on-ul e gratuit cu pass-ul curent | Acoperit indirect — aceeași funcție de validare rulează pe fiecare linie din coș la checkout |
| Add-on gratuit inclus cu pass-ul | Bifat automat + input ascuns (singurul caz legitim rămas pentru input-ul necondiționat) | Ignoră verificările de tip/specializare/pass | Prețul e adus la zero prin dentisity_free_product_ids() la calculul totalurilor coșului |
| Credite gratuite de workshop din pass | Nu blochează selecția — se aplică ulterior | N/A la momentul adăugării | Cele mai scumpe workshop-uri eligibile sunt aduse la zero, până la epuizarea creditelor rămase |
Modul „specific" de workshop-uri permise pentru un pass și starea de epuizare a stocului nu sunt re-validate pentru elemente aflate deja în coș la checkout — doar la momentul adăugării și în bucla proprie a formularului de înregistrare. Impactul e limitat, deoarece pluginul nu oferă nicio altă cale de adăugare a workshop-urilor în coș în afara acestui flux controlat (nu există pagină de coș vizibilă participanților). Rămâne un candidat pentru întărire ulterioară, dar nu afectează integritatea achizițiilor în fluxul actual.