| name | gestioneaza-personal |
| description | Personalul — angajați (PIN/parolă), roluri & permisiuni, ture cu raionul corect pentru rutarea QR, Program Salon, contracte și salarizare, beneficii de masă cu buget, schimbarea unității. La „adaugă angajat", „programează tură", „pune ospătarul pe raion", „de ce nu intră comanda QR la ospătar", „fă contract", „masa personalului / beneficiu personal", „de ce nu se aplică beneficiul". |
Gestionează personalul — angajați, roluri, ture, raioane, contracte
Citește întâi knowledge/agent-operare-avansata.md pentru standardul de execuție sigură, apoi knowledge/personal-hr.md (modal de tură câmp-cu-câmp, Program Salon, ladderul de rutare QR, unitatea) și secțiunea „⚠ De știut la scrieri prin MCP" din knowledge/tools-mcp.md. Pentru beneficii de masă personal (mâncare/băutură gratis/redus cu buget + de ce NU discount/din partea casei) citește knowledge/beneficiu-personal.md și vezi (g) mai jos. Pentru sarcini & checklist-uri vezi skill-ul separat gestioneaza-sarcini. Turele de producție (fabrică) sunt alt concept — nu le confunda.
Tot ce e scriere cere modulul personal pe token. Context mereu întâi: list_brands + list_locations (ai nevoie de brandId/locationId). Roluri: list_entities(entityType:"roles", brandId). Stare curentă pe brand/unitate: get_staff_overview(brandId, locationId). Pentru inventar rapid pe tot tenantul poți folosi list_employees(brandId?), dar brandId doar etichetează scope (own/shared/global/other) și roleBrand; nu filtrează lista.
Contracte/salarii/pontaj/concedii — tool-uri MCP live: create_employee_contract / update_employee_contract, upsert_employee_monthly_salary, create_time_entry / update_time_entry, create_leave_request / update_leave_request, plus citirile list_employee_contracts / list_leave_requests. Se apelează direct. Detalii pe fiecare la (e).
Regula de aur
ID-uri, nu nume (roleId, employeeId, floorConfigId). Caută înainte de a crea (create_employee face dedupe doar pe nume exact). După scriere verifică prin CITIRE, nu prin UI. Ștergerea de angajați/ture întregi NU se face prin conexiune — îndrumă userul în aplicație.
(a) Adaugă angajat + parolă/PIN
create_employee(firstName*, lastName*, brandId*, roleId, email, phone, locationId, hireDate, baseSalary, nickname, pin). Obligatorii: firstName + lastName + brandId — email-ul NU e obligatoriu. baseSalary (lunar brut) calculează automat tariful orar la 168h/lună. Poreclă = numele afișat peste tot.
- ⚠ CAPCANĂ: angajatul creat prin MCP poate să NU apară în Lista Personal cât timp ești pe unitatea lui — lista filtrează după unitățile salvate pe fișa angajatului, iar fișa creată prin conexiune nu le are încă completate. Soluție: după creare, spune-i userului să-l vadă pe „Toate unitățile" SAU să deschidă o dată fișa în aplicație și să o salveze (completează unitățile). Tu confirmă mereu prin
get_staff_overview(brandId) — acolo apare corect.
- PIN-ul SE poate seta prin MCP —
create_employee(..., pin:"4321") sau update_employee(employeeId, pin) chiar funcționează (confirmă cu get_staff_overview → angajatul apare cu pin:"setat"). Parola NU se trimite prin MCP: din fișa angajatului → „Copiază link setare parolă" (48h) → i-l trimiți, și-o alege singur. Și PIN-ul are link similar dacă preferi ca el să-l aleagă.
- ⚠ Câmpul PIN apare DOAR dacă rolul are permisiunea
pin_login — altfel link-ul/câmpul de PIN nu există. Pune pin_login pe rol (vezi (b)) sau alege un rol care o are.
- Import în masă:
bulk_create_employees(brandId*, employees[], locationId).
(b) Creează/editează rol + permisiuni
Pentru roluri + permisiuni ai un skill dedicat, mai bogat: configureaza-roluri (înțelege întâi ce vede fiecare rol, apoi acționează). Tool-urile de ÎNȚELEGERE cer citire pe modulul personal: list_permission_catalog (vocabularul complet de drepturi), describe_role(roleId|roleName) (ce permisiuni are un rol + ce pagini vede + câți angajați), preview_role_access(permissions[]) (ce pagini ar da un set de chei, ÎNAINTE de creare), list_role_presets, suggest_role_setup(description) (schiță de rol dintr-o descriere), plus find_role_for_job(job) și plan_location_roles(brandId) — cele două cu care VERIFICI dacă rolul cerut există deja, înainte să creezi ceva. Folosește-le ca să nu bifezi orb și să nu duplici roluri.
- ÎNTÂI verifici dacă rolul cerut există deja:
find_role_for_job(job) (rol real ▸ rol prestabilit ▸ abia apoi custom). La o locație nouă, plan_location_roles(brandId) îți spune ce roluri îi lipsesc. Nu porni niciodată direct cu create_role.
- Rapid, din prestabilite:
apply_role_presets(roleNames[]*, brandId?, confirm:true) — creează exact rolurile cerute cu permisiunile gata setate, fără duplicate. Pentru TOT setul unei verticale: seed_default_roles(brandId*, businessType*) (restaurant|bar|cafenea|fast_food|hotel_restaurant|catering…), idempotent.
- Rol chiar nou (nu există ca prestabilit):
create_role(name*, brandId*). ⚠ NU trimite permissions ca OBIECT la create_role (ex. {pos:true}) — se salvează greșit (rolul rămâne fără permisiuni reale) ȘI strică apoi apelurile incrementale set_role_permissions(addPermissions) (dau eroare). Creează rolul gol, apoi pune permisiunile cu set_role_permissions (array).
- Permisiuni fin:
set_role_permissions(roleId*, permissions[] | addPermissions[] | removePermissions[]) — toate iau array de string-uri. permissions ÎNLOCUIEȘTE tot setul (folosește-o întâi pe un rol nou); add/removePermissions merg incremental DOAR dacă setul curent e deja array (pe un rol creat greșit cu obiect, „repară-l" întâi cu permissions).
- ⚠ NU există validare pe chei — orice string e acceptat tăcut, inclusiv chei greșite care NU deblochează nimic. Folosește DOAR cheile reale din
knowledge/personal-hr.md → „Catalog permisiuni". „Toate dintr-o categorie": all:<categorie> (categorii valide: pos, client_orders, delivery, payments, kitchen, reservations, inventory, menu, staff, tasks, cleaning, haccp, marketing, ai_assistants, sales_crm, finance, cashbook, analytics, ecommerce, ) acordă tot grupul; = super-admin (doar Admin/Proprietar). Chei uzuale: , , , , , , , (pontaje/prezență self-service). Pentru un rol standard întreg, preferă (pasul 1).
(c) Programează tură cu raionul corect (ca să meargă rutarea QR)
- Verifică ÎNTÂI Program Salon pe ziua respectivă: aranjamentul activ pe acea zi trebuie să conțină raionul pe care vrei să pui ospătarul. Dacă raionul nu există în aranjamentul zilei, nu-l poți programa și comanda QR nu va prinde. (Configurare Program Salon → (d).)
- ⚠ DOUĂ evidențe de tură separate — alege corect:
create_shift(employeeId*, date*, startTime*, endTime*, brandId*, locationId*, floorConfigId, sectionId, sectionName) creează tura de lucru (pontaj / „tură deschisă POS"). ARE raion (sectionName, ex. „Terasă"; mai multe = „Terasă,Bar") → rutează QR-ul pentru ziua aia, DAR NU apare în Planificator Ture (planificatorul citește doar programul publicat).
create_staff_schedule(employeeId*, scheduledStart*, scheduledEnd*, floorConfigId, locationId, status) creează intrarea de program publicat → apare în Planificator Ture. DAR nu are param de raion (raionul rămâne gol) și nici de brand.
- Concluzie: niciun tool MCP nu face singur «intrare în planificator CU raion». Reguli: vrea doar rutare QR azi →
create_shift cu sectionName. Vrea programul în planificator → create_staff_schedule (status:"published"), apoi pune raionul din aplicație (Planificator → tură → Secțiune Atribuită) sau spune-i clar că raionul se setează în UI. Editare: update_shift(shiftId*, …, sectionName?). Mai multe: bulk_create_shifts / bulk_create_staff_schedules.
- ⚠ Timezone: orele trimise se stochează ca UTC și se afișează cu offset-ul local (vara RO = +3h → „10:00" apare „13:00"). Dacă userul vrea fix 10:00 local, scade offset-ul sau avertizează-l de decalaj.
- ⚠
dayOfWeek (Program Salon): 0=Duminică … 6=Sâmbătă — calculează ziua REALĂ a datei, nu presupune.
create_staff_schedule cu status:"draft" = ciornă (invizibilă); "published" = vizibilă angajatului. (Tura creată cu create_shift nu are draft/publish — există direct.)
- Confirmă:
get_staff_overview(brandId, locationId) arată turele viitoare cu raionul (sectionName); pentru detaliu fin poți folosi accesul SQL read-only. (get_shift_detail e pentru turele de producție, nu pentru turele de personal — nu-l folosi aici.)
(d) Configurează Program Salon (aranjament per zi)
Pregătire aranjamente (dacă lipsesc): create_floor_zone → bulk_create_floor_tables → create_floor_config(name, brandId, locationId, zoneIds) → add_sections_to_config(floorConfigId, sections:[{name,color}]) → assign_tables_to_section(floorConfigId, sectionId, tableDbIds[]). assignedCount de la asignare înseamnă mese DISTINCTE, nu desktop+mobile dublat; confirmă prin get_floor_config(section:"tables"). Apoi ce aranjament pe ce zi: create_floor_config_schedule(brandId*, dayOfWeek* [0=Duminică…6=Sâmbătă], floorConfigId*, locationId*) pentru fiecare zi de operare. Excepțiile pe dată și presetul QR per raion (Prenume / La scanare / Confirmare ospătar) se setează din tabul Program Salon în aplicație.
(e) Contracte CIM/PFA/Zilier + alocări + bonusuri
MCP live (se apelează direct): create_employee_contract(employeeId*, contractKind* [cim|srl_pfa|zilier|fara_contract], monthlyGrossSalary | monthlyNetSalary | dailyRate [zilier], allocations [allocationType toate de același tip: percent ≤ 100 SAU fixed ≤ brut], bonuses [bonusType: monthly_fixed|percent_sales|per_day|one_time]); editare update_employee_contract(contractId*, …) — ⚠ allocations/bonuses ÎNLOCUIESC tot setul (trimite lista întreagă), lasă-le nesetate ca să le păstrezi. Salariul lunii: upsert_employee_monthly_salary(employeeId*, year*, month*, grossSalary, netSalary, bonuses) — unic pe (angajat, an, lună), reapelarea suprascrie. Pontaj: create_time_entry(employeeId*, clockIn?, clockOut?) (guard idempotent: pontaj deschis nu se dublează) → închizi cu update_time_entry(timeEntryId*, clockOut). Concedii: create_leave_request(employeeId*, type* [concediu_odihna|medical|personal|fara_plata|eveniment_special], startDate*, endDate*) → update_leave_request(leaveRequestId*, status [pending|approved|declined|cancelled], reviewedBy?, reviewNote?). Confirmă valorile cu userul înainte de scriere.
Pontaj self-service (mobil): pe lângă pontajul cu PIN, angajații se pot ponta singuri din aplicația Symbai Staff, ecranul Pontaj — intrare/ieșire cu GPS (opțional selfie) și pauze cu motiv; telefoanele folosite se înregistrează per angajat, cu politică de avertizare sau blocare la telefon necunoscut. Managerul vede prezența (cu comparație planificat-vs-real) în /staff → tab „Pontaje (prezență)". Accesul se controlează din permisiunile attendance_view (vede pontajele) / attendance_manage (le administrează) — vezi (b).
(f) Schimbă unitatea (brand/locație)
Selectorul de unitate (sus, valabil pe TOATE paginile, nu doar /staff) filtrează lista de personal și contextul. Comutarea unității nu se face prin MCP (e stare de browser) — rețeta canonică (dropdown prin Chrome, recomandat; sau URL ?unit=brandId-locationId; gotcha userMutated; id-uri din list_brands/list_locations) e în knowledge/navigare.md, secțiunea „Schimbarea unității active". Aici, doar contextul HR: ca să CITEȘTI personalul unei unități fără să comuți, get_staff_overview(brandId, locationId).
(g) Beneficii de masă pentru personal (mâncare/băutură gratis/redus)
Detaliile complete (discovery cu owner, configurare câmp-cu-câmp, cum aplică ospătarul, cum citești rapoartele) sunt în knowledge/beneficiu-personal.md. Esențialul:
- Întâi explică regula de aur: consumul angajaților trece DOAR prin „Beneficiu personal", NU prin „Reducere" și NU prin „Din partea casei". Reducerea/comp scad VENITUL (sunt pentru clienți); beneficiul personal intră pe linia de cost de personal, cu registru per angajat și buget. Canalul greșit corupe P&L-ul și rapoartele (vânzări subevaluate, comp umflat, zero atribuire/buget).
- Fă discovery înainte de configurare: cine consumă (toți/rol/nominal), ce produse (tot meniul/mâncare/categorie), ce excluderi clare există (alcool, produse scumpe, taguri/tipuri), cum (gratis cu buget / preț special / procent), cât (buget/perioadă + la depășire blochează/avertizează/permite), când (oricând / doar la lucru), cine aplică (self/manager) și dacă aplicarea cere aprobare manager. Apoi propune un setup și confirmă cifrele înainte să scrii.
- BENEFICIAR ≠ APLICATOR: beneficiarul mănâncă, aplicatorul pune beneficiul pe notă. Confuzia asta e cea mai frecventă.
- MCP (dacă tool-urile sunt active pe instanță): citire
list_staff_benefit_rules, diagnose_staff_benefit_rule („de ce nu se aplică"), get_staff_benefit_budget, get_staff_benefit_report; scriere create_staff_benefit_rule, update_staff_benefit_rule, set_staff_benefit_employee_budget, toggle_staff_benefits, set_staff_benefit_accounting_mode. Regulile acceptă și excluderi (excludedProductIds, excludedTagIds, excludedProductTypeCodes) plus requiresApproval; aprobarea/respingerea unei cereri se face în POS/UI dacă nu vezi tool dedicat în sesiune. Înainte de set_staff_benefit_accounting_mode, explică owner-ului diferența: internal_only rămâne intern/P&L, separate_benefit trimite valoarea distinctă către contabilitate ca avantaj în natură, fără să dubleze COGS. Dacă tool-urile lipsesc din sesiune → fallback la pagină /staff → tab „Beneficii Personal" (vechiul /settings/staff-benefits doar redirecționează).
- Aplicarea pe o notă rămâne în POS (acțiune de ospătar: masă → 3 puncte → „Beneficiu Personal"); prin conexiune o explici, n-o faci.
- „Nu se aplică" = aproape mereu CONFIG, nu defect. Verifică în panoul „Stare regulă" / cu
diagnose_staff_benefit_rule: rol beneficiar ∈ permise, la lucru dacă se cere, produs în scope și nu în excluderi, buget neepuizat, aplicator permis, iar dacă este activ vezi cererea în așteptare.
Click-paths în aplicație (extensia Chrome, când MCP nu acoperă)
După ce userul e logat în aplicația lui:
- Angajați:
/staff?tab=list → „Adaugă Angajat" / „Editare" → link-uri parolă/PIN pe fișă.
- Roluri:
/staff?tab=roles → „+" rol nou sau „✨" roluri prestabilite; comutatorul „Toate (N)" per categorie.
- Ture:
/staff (Planificator Ture) → celulă goală → completezi Angajat/Dată/Ore/Aranjament Sală/Secțiune Atribuită/Status → „Salvează și Publică Program".
- Program Salon:
/staff?tab=floor-schedule → per zi alegi aranjament + preset QR per raion; excepții pe dată.
- Contracte:
/staff?tab=contracts.
- Beneficii Personal:
/staff → tab „Beneficii Personal" (vechiul /settings/staff-benefits redirecționează) → comutator global + tab Reguli (creează/editează, inclusiv excluderi și „Doar cu aprobare") → la POS, aplicarea pe masă din „Acțiuni Masă" (3 puncte) → „Beneficiu Personal"; aprobările cererilor se fac în POS/UI dacă nu există tool dedicat în sesiune.
- Pontaje (prezență):
/staff → tab „Pontaje (prezență)" — prezența din aplicația Symbai Staff (GPS, selfie, pauze, telefoane per angajat, planificat-vs-real).
„De ce comanda QR nu intră la ospătarul corect?" — diagnostic
Mergi pe ladder (vezi knowledge/personal-hr.md), în ordine:
- Raion gol pe tură/pontaj — managerul n-a pus Secțiune Atribuită, sau ospătarul a deschis tură din POS fără raion. (Cea mai frecventă.)
- Masa nu e în aranjamentul zilei — adăugată după ce s-a făcut aranjamentul, sau nume diferit (majuscule/spațiere) → raionul mesei iese gol.
- Raion scris diferit — „Terasă" vs „Terasa" (diacritice/majuscule) → nu se potrivesc.
- Niciun aranjament programat azi în Program Salon → fallback pe alt aranjament care poate nu conține masa.
- Tura e ciornă sau în afara intervalului orar → nu e „activă acum".
Verifică tura din Planificator (raion corect, publicată, interval) + aranjamentul zilei din Program Salon (masa e în el, raionul scris identic). Citește live cu
get_staff_overview(brandId, locationId) (turele viitoare cu raionul) sau, la nevoie, cu accesul SQL read-only pe evidențele de ture/aranjamente. Nu presupune. (get_shift_detail e pentru turele de producție, nu de personal.)
Verifică prin CITIRE (nu prin UI)
După orice scriere: get_staff_overview (angajați, roluri, ture viitoare, pontaje) / list_entities confirmă; pentru contracte list_employee_contracts(employeeId?, active?), pentru concedii list_leave_requests(employeeId?, brandId?, status?). Interfața se actualizează abia după refresh — succes la tool = salvat, nu repeta scrierea. Dacă modulul personal nu e pe token („permisiune insuficientă"), explică activarea din portal Hub → Acces AI. Audit pe un angajat: jurnal_activitate(categorie:"STAFF", angajatId).