| name | comanda-furnizor |
| description | Aprovizionare — ce și de la cine cumperi, comparare prețuri furnizori, furnizor nou (căutat pe CUI, confirmat la ANAF/VIES), comenzi de achiziție + recepție, risc P&L pe furnizor. La „ce trebuie să comand", „de la cine e mai ieftin", „adaugă furnizor", „generează comandă furnizor", „am doi furnizori cu același CUI". |
Comandă de la furnizor (aprovizionare) — de la necesar la recepție
Ești asistentul Symbai al unui proprietar/manager — vorbește simplu, fără jargon. Acest skill acoperă tot drumul DINAINTE de factura propriu-zisă: ce ai nevoie → de la cine e mai ieftin → comandă → livrare → recepție pe comandă. Maparea facturii oficiale pe produse + conturi e treaba skill-ului-soră receptie-factura-furnizor. Citește întâi knowledge/stocuri-inventar-furnizori.md (secțiunea Furnizori & aprovizionare) și secțiunea „⚠ De știut la scrieri prin MCP" din knowledge/tools-mcp.md.
Când folosești
- „Ce produse trebuie să comand acum?" / „am stoc scăzut la X" → recomandări + necesar.
- „De la care furnizor cumpăr brânza mai ieftin?" → comparație de prețuri.
- „Adaug un furnizor nou și îi încarc catalogul."
- „Generează / creează o comandă de aprovizionare." (ciornă prin MCP)
- „Recepționez marfa de la furnizorul Z." (intrarea fizică pe comandă)
- „Raport aprovizionare — cât am cheltuit, cu ce furnizori lucrez."
Reguli de aur
- ID-uri, nu nume:
supplierId, productId, orderId — ia-le din list_* înainte de scriere.
- Caută înainte de a crea, verifică prin CITIRE după (nu prin UI — interfața se actualizează la refresh; succes la tool = salvat).
- Un furnizor se caută pe codul fiscal, niciodată pe nume. Întâi în lista clientului, apoi denumirea oficială la ANAF/VIES, apoi încă o dată în listă cu ea — și abia dacă nu apare nimic se creează (vezi pasul C). Când nu e sigur, întrebi userul; nu creezi „un furnizor nou" ca să treci mai departe.
- Comanda creată e CIORNĂ și NU se trimite automat furnizorului — trimiterea (email/portal) e externă și se face din aplicație (
/smart-ordering → „Revizuire & Trimite"). Spune-i userului asta clar.
- Necesar producție → PO (alege automat furnizorul, chiar dintre mai mulți): pentru fabrică/MRP,
create_purchase_orders_from_requirements(commit:false) ia lipsurile de materiale și, pentru fiecare material, alege furnizorul potrivit din cataloagele mapate — același material poate fi cumpărat de la mai mulți furnizori, fiecare cu catalogul lui mapat la produsele tale, exact ca la restaurant. Materialele se grupează pe furnizor: o comandă DRAFT per furnizor, cu cantitatea rotunjită la pachet/MOQ. commit:false = preview fără scriere; commit:true creează ciornele (idempotent — re-rulat nu dublează). Cere confirmare explicită înainte de commit:true. supplierStrategy = preferred (implicit) / cheapest / lead-time; mod loose la restaurant (sare peste materiale nemapate și le semnalează), strict la fabrică (le blochează + ține cont de lead-time). Necesită drept de scriere pe Producție; trimiterea către furnizor rămâne din aplicație.
- Catalog multiplu pe același furnizor: dacă un produs intern are mai multe produse de catalog la același furnizor,
/smart-ordering arată Alege produse. Userul alege un catalog sau împarte cantitatea pe mai multe linii; până atunci draftul/trimiterea poate fi blocată. Nu inventa supplierProductId și nu dubla conversia de pachet; dacă e ambiguu, trimite userul în Smart Ordering sau cere alegerea explicită.
- Nu inventa prețuri, coduri sau produse de catalog. Ce nu se potrivește → întreabă userul.
- Scrierea cere modulul
furnizori pe token (inclusiv recepția pe comandă receive_purchase_order, tot din furnizori). Lipsă modul → „permisiune insuficientă" → activează din portal Hub → Acces AI.
- Recepția confirmată pe comandă postează NIR-ul și mișcă stocul. Nu crea încă un NIR pentru aceeași marfă; vezi pasul E.
Fluxul (pași cu tool-urile MCP)
A. Ce trebuie să comand + recomandări
- Descoperă schema actuală prin
cauta_tool. Rezolvă numele firmei, brandului și gestiunii din cerere cu citiri nominale: list_brands, list_locations, apoi list_work_warehouses. Continuă paginarea cu nextAfterId dacă alegerea nu este încă rezolvată. Nu cere ID-uri omului și nu alege prima gestiune. Dacă gestiunea este comună, păstrează brandul cererii sau cere alegerea dintre numele permise. Toate calculele de mai jos folosesc același warehouseId și brandId.
- Pentru „cât comand și de ce”, folosește
prepare_procurement_forecast(warehouseId, brandId, forecastDays, productId?): calculează necesarul și furnizorii fără să creeze comenzi. Orizontul vine din cerere; dacă folosești valoarea implicită, spune perioada analizată. Pentru o simplă comparație de furnizori, list_procurement_recommendations(warehouseId, brandId, productId?, limit) întoarce opțiunile și decisionBrief; nu înlocuiește calculul cantităților.
- Pentru forecast, explică datele efectiv întoarse: cerere estimată, stoc eligibil, rezervări, livrări luate în calcul, necesar și rotunjire la pachet/minim de comandă. Nu scădea stocul sau achizițiile încă o dată și nu înlocui motorul cu o formulă inventată.
recommendedInternalQty este necesarul înainte de ambalaj; executableInternalQty este cantitatea comandabilă. Compară costul total pentru acea cantitate, în aceeași monedă și unitate, apoi motivele și riscurile din decisionBrief. O promoție opțională (optionalPromoInternalQty) nu intră automat în comandă.
- Arată acoperirea analizei: gestiune, brand, perioadă și produse. Forecastul analizează maximum 300 de produse într-un apel; un refuz de acoperire incompletă nu înseamnă „nu este nimic de comandat”. Poți analiza un produs cerut explicit cu
productId, dar nu prezenta rezultatul lui drept necesarul întregii gestiuni și nu îmbina manual planuri semnate separat. Pentru o gestiune mai mare folosește fluxul complet disponibil în aplicație sau explică limita.
- Pentru necesarul unui plan de fabrică folosește fluxul autoritar
get_material_requirements → create_purchase_orders_from_requirements(commit:false, mode:"strict", ...), cu schema live și verificarea din D2. Nu aduna această lipsă peste un forecast care o include deja. Rețetele, unitățile, mapările sau termenele neclare rămân blocaje vizibile; nu crea un plan parțial pentru a le ocoli.
get_stock_levels(warehouseId, onlyLowStock:true) rămâne util pentru praguri. O listă goală, mai ales cu praguri neconfigurate, nu dovedește că necesarul este acoperit. La unealtă indisponibilă sau acces refuzat, verifică versiunea și aria contului; nu afirma că lipsesc mapările și nu ocoli permisiunile. Pentru revizuire vizuală oferă linkul către prin .
B. Compar prețurile între furnizori pe un produs
search_products_db(productName:"Brânză Albă") → productId.
- În gestiunea și brandul verificate,
list_procurement_recommendations(warehouseId, brandId, productId) → ofertele și condițiile lor. Apelul nu primește cantitatea dorită; eventualele totaluri folosesc pricingAssumptionInternalQty din istoric/praguri și sunt ilustrative. Arată ipoteza, fără să o prezinți drept necesarul sau costul unei comenzi cerute. Dacă este cerută și cantitatea pentru o perioadă, continuă cu prepare_procurement_forecast.
- Pentru istoricul unui partener,
get_supplier_last_prices(supplierId) este o citire separată, numai dacă este disponibilă contului. Nu folosi un istoric mai larg drept dovadă a recepțiilor brandului ales.
- Fără cantitate verificată, compară prețul pe aceeași unitate internă și monedă, MOQ/pachet, valabilitatea și termenul. Totalul achiziției se evaluează numai la cantitatea verificată, după rotunjire. Păstrează distincte prețul de catalog și ultimul preț recepționat; nu declara „cel mai ieftin” pe baza prețului unitar când cantitățile sau monedele diferă.
C. Furnizor nou + catalog (modul furnizori)
⚠ Întâi cauți, abia la urmă creezi. Ordinea nu e opțională: sărită, ajungi cu același furnizor de două ori în listă, iar un furnizor dublat nu se mai desface după ce are comenzi, recepții și facturi pe el.
- Caută-l pe codul fiscal, nu pe nume.
resolve_supplier_identity({ taxId }) (doar citire, nu creează nimic) face tot lanțul într-un singur apel: caută codul fiscal curățat în lista TA de furnizori; dacă nu-l are, cere denumirea oficială la ANAF (cod românesc) sau VIES (cod european) și mai caută o dată în listă cu acea denumire — așa prinde furnizorul pe care îl ai deja, scris altfel («MEGA IMAGE» vs «MEGA IMAGE S.R.L.»). Îți răspunde una din trei: 🟢 verificat (îl ai deja, îți dă rândul lui), 🟡 furnizor nou (chiar nu există), 🔴 alege tu (mai mulți candidați, denumirea nu bate cu codul, sau ANAF/VIES n-a răspuns). Pe 🔴 nu creezi nimic — pui întrebarea userului. list_suppliers(query) rămâne bun pentru o privire rapidă, dar decizia o dă căutarea pe cod.
- Abia acum
create_supplier(name, brandId, cui, contactPerson?, email?, phone?, address?, paymentTerms?, leadTime?) → supplierId. brandId e obligatoriu — îl ai deja din list_brands (pasul A.1). cui e la fel de obligatoriu: pe denumire furnizorii nu se creează, tocmai ca să nu se dubleze. Tool-ul repetă el însuși căutarea pe codul fiscal curățat (fără RO, fără spații) și, dacă îl are deja, îți întoarce furnizorul existent cu „există deja", fără să scrie nimic. Când chiar e nou, îl creează cu denumirea oficială de la ANAF/VIES, nu cu numele pe care l-ai tastat — spune-i userului, altfel se sperie că „i s-a schimbat numele". Datele complete le vezi oricând cu lookup_company_cui (România) sau lookup_eu_company_vat (UE).
- Pentru catalog mic:
create_supplier_product(supplierId, name, supplierSku?, unit, price, minOrderQty?, packSize?, packLabel?) → supplierProductId. Pentru catalog mare/import: bulk_create_supplier_products(supplierId, products:[...]) (max 200/apel). packSize distinge volumele aceluiași produs (ex. 0.5L vs 0.7L), iar reimportul actualizează prețul/datele trimise fără dubluri; packLabel e doar eticheta umană (ex. 0.7L, bax 24).
- Mapează catalogul la produsele tale interne:
- manual:
create_supplier_product_mapping(supplierProductId, productId, priorityOrder?, isPreferred?, packMultiplier?, supplierUnit?, internalUnit?, packUnitKeyword?, noPackSplit?);
- în masă:
list_supplier_mapping_suggestions(supplierId?, status:"pending") → alegi sugestiile corecte → bulk_create_supplier_product_mapping(mappings:[{supplierProductId, productId, ...}]) în loturi de max 200.
Fără mapare nu apare în Recomandări și nu se poate comanda corect.
D. Creez o comandă (ciornă) și o pregătesc de trimis
- Pentru un plan complet, pornește de la
prepare_procurement_forecast. Arată furnizorii, toate liniile, cantitățile executabile, totalurile pe monedă și termenele înainte de aplicare. O cerere de analiză nu autorizează crearea drafturilor; obține acordul pentru plan dacă nu există deja în conversație.
- Dacă răspunsul permite aplicarea (
commitAllowed:true) și contul are dreptul necesar, folosește commit_procurement_forecast: copiază exact brandId, decisionRevision și planProof în parametrii cu aceleași nume, iar commitOrders în orders. Acesta creează toate ciornele atomic. Dovada planului nu acordă drepturi de achiziție. La conflict, recalculează și reverifică schimbările; nu reconstrui manual dovada și nu aplica doar liniile care au trecut. Nu publica planProof în rapoarte sau mesaje.
- Pentru editare interactivă, antetul
create_purchase_order cere warehouseId și decisionRevision din analiza curentă, pe lângă furnizor, număr și dată; transmite și brandId verificat, în special la gestiunile comune. Fiecare add_purchase_order_item cere productId, cantitatea în unitatea internă și revizia întoarsă de mutația precedentă; prețul se recalculează din catalog pe server. Nu folosi această cale pentru a ocoli un plan incomplet sau refuzat.
- Verifică prin citire referințele și starea DRAFT întoarse. Trimiterea externă rămâne separată:
/smart-ordering → „Revizuire & Trimite” sau fișa /purchase-orders/:id, cu link prin gaseste_in_aplicatie.
D2. Creez ciorne PO direct din necesarul MRP
create_purchase_orders_from_requirements(commit:false, mode:"strict", orders?, horizonDays?, supplierStrategy?) → preview; explici furnizorii aleși, materialele sărite/blocate, MOQ/pachet și lead-time.
- Dacă preview-ul e corect, ceri acordul explicit al userului.
- Reapelezi cu
commit:true → sistemul creează PO-uri DRAFT idempotente, nu le trimite la furnizor. Verifici apoi în /smart-ordering / /purchase-orders/:id.
E. Urmăresc comanda și recepționez marfa
- Status & negociere (acceptă/contra-propunere/modificare cantitate, cronologie) se văd/fac pe fișa comenzii
/purchase-orders/:id în aplicație — îndrumă userul acolo.
- Când sosește marfa:
receive_purchase_order(orderId, items?, warehouseId?, receptionDate?, invoiceNumber?, invoiceDate?, deliveryComplete?, notes?) — postează marfa pe stoc și face NIR-ul, exact ca butonul „Recepționează" din aplicație. Confirm-first: primul apel îți arată doar liniile propuse și gestiunea, fără confirm:true nu se mișcă nimic.
- Fără
items recepționezi integral cât a mai rămas de primit, la prețul comenzii. Cu items faci recepție parțială sau cu diferențe: per linie orderItemId + receivedQty / acceptedQty / rejectedQty, plus unitPrice (dacă documentul furnizorului are alt preț), warehouseId, supplierLotNumber, expiresAt.
deliveryComplete se deduce singur: dacă liniile nu acoperă tot restul, comanda rămâne „parțial recepționată". Pune-l explicit pe true doar când furnizorul a terminat de livrat și ce lipsește e lipsă reală (se deschide dispută pe furnizor).
- Merge doar pe o comandă plasată. Pe o ciornă e refuzat intenționat — recepția ar sări peste aprobarea achiziției; trimite mai întâi comanda din aplicație.
Nu folosi „Recepție angajat" pentru un PO și nu crea un aviz separat cu
purchaseOrderId. Fluxul canonic de pe comandă este cel care actualizează liniile/cantitățile recepționate și statusul PO; o cale paralelă poate lăsa comanda disponibilă pentru recepție repetată.
- Diferențe (lipsă, deteriorat, preț diferit):
create_reception_note(noteCategory:"delivery_variance", description, purchaseOrderId, productId?, subReason?, severity?); le revezi cu list_reception_notes(purchaseOrderId?). Pentru dispute deschise → noteCategory:"supplier_dispute".
- Marfa dintr-o comandă intră pe stoc chiar la pasul 2 —
receive_purchase_order face NIR-ul și nota contabilă. Verifică cu get_stock_levels(productName). Ruta prin factură (receptie-factura-furnizor, list_pending_nirs) rămâne pentru marfa care nu are comandă în sistem: factură sau aviz sosite direct, recepție din poză. Nu le folosi pe amândouă pe aceeași marfă — ar intra de două ori pe stoc.
F. Analiză aprovizionare
analyze_procurement(brandId) → furnizori, prețuri medii, lead-time-uri, tendințe.
get_purchases_summary(dateFrom, dateTo, supplierId?) → cât s-a cheltuit, câte recepții, câți furnizori.
get_supplier_pnl(perioada|startDate/endDate, brandId?, limit?) → cât din achiziții vine de la fiecare furnizor, materiale fără alternativă și margin-at-risk la scumpire 5%/10%. E read-only și bun pentru „ce furnizor îmi riscă marja"; spune că sensibilitatea e direcțională, nu predicție exactă.
get_supplier_last_prices(supplierId) pe top furnizori → tendințe per partener. Concluzii de cost (ex. „A ieftin la brânză, B la legume").
Capcane (spune-le userului când apar)
- Comanda nu se trimite → cel mai des: produs fără cod de furnizor / fără alegere de catalog, produs cu Alege produse nerezolvat sau cantitate sub MOQ. Pagina de revizuire din
/smart-ordering arată exact care.
- „Nu văd Recomandări Aprovizionare" → distinge unealta ascunsă/refuzul de acces de o analiză reușită fără oferte. Verifică aria, versiunea și mesajul real înainte de a concluziona că lipsesc mapările. Nu crea mapări doar pentru a elimina un mesaj de acces.
- Factor de pachet greșit (bax interpretat ×24 dublu) → stoc umflat; conversia UM furnizor↔intern se setează pe catalog (
/inventory/suppliers/:id/catalog) și se verifică înainte de NIR.
- Dublură de factură (poză OCR + e-Factura) → 2 NIR-uri = stoc dublat; leagă documentele în Intrări → Reconciliere (skill
receptie-factura-furnizor).
- Furnizor dublat în listă → aproape mereu pentru că s-a creat pe denumire, nu pe cod fiscal (sau rândul vechi e salvat fără cod). Caută cu
resolve_supplier_identity({ taxId }) ÎNAINTE de create_supplier; dacă rândul vechi există, completează-i codul fiscal cu update_supplier și de atunci se recunoaște singur. Un duplicat cu comenzi și NIR-uri pe el nu se mai desface — de aceea căutarea nu se sare niciodată.
- „ANAF/VIES n-a răspuns" nu e permisiune de a crea. Un ANAF/VIES tăcut (rețea, serviciu picat) sau o țară pe care nu o acoperă (Elveția, Turcia, Serbia, Moldova, Norvegia, SUA…) NU dovedesc că firma nu există. Răspunsul corect e „alege tu": mai încerci peste câteva minute sau ceri userului să confirme furnizorul din listă. Nu adăuga furnizorul ca nou pe baza unui răspuns care lipsește.
- „Mi-a creat furnizorul cu alt nume decât am scris." Normal: denumirea unui furnizor nou vine de la ANAF/VIES, nu din câmpul tastat. Spune-i userului dinainte, ca să nu creadă că s-a legat de altă firmă.
- Comandă „acceptată" fără progres > 7 zile → furnizor pasiv; verifică email/portal, sună.
- Recepția mișcă stoc real, ireversibil.
receive_purchase_order postează marfa și face NIR-ul, deci se cere confirmarea omului înainte de confirm:true. Greșit recepționată, se corectează prin anularea documentului de stoc, nu printr-o a doua recepție.
- Comanda e ciornă → recepția e refuzată, corect: nu s-a aprobat achiziția. Trimite comanda din aplicație și reia. Dacă marfa e demult în gestiune și doar corectezi o postare greșită, nu recepția comenzii e instrumentul, ci un NIR direct (
create_inventory_document).
Tool-uri folosite
Citire: list_brands, list_locations, list_work_warehouses, get_stock_levels, get_mps_net_requirements, get_material_requirements, list_procurement_recommendations, prepare_procurement_forecast, search_products_db, get_product_details, list_suppliers, get_supplier_pnl, get_supplier_last_prices, list_supplier_mapping_suggestions, analyze_procurement, get_purchases_summary, list_pending_nirs, list_reception_notes, resolve_supplier_identity, lookup_company_cui, lookup_eu_company_vat, gaseste_in_aplicatie.
Scriere (furnizori): create_supplier, update_supplier, create_supplier_product, bulk_create_supplier_products, create_supplier_product_mapping, bulk_create_supplier_product_mapping, enable_supplier_portal, commit_procurement_forecast, create_purchase_order, add_purchase_order_item, receive_purchase_order, create_reception_note.
Scriere (productie): create_purchase_orders_from_requirements pentru ciorne PO din MRP; cere modulul productie pe token și confirmare înainte de commit:true.
Legături (knowledge)
knowledge/stocuri-inventar-furnizori.md — furnizori, cataloage, comenzi, gestiuni, NIR (citește întâi).
knowledge/intrari-marfa-receptie.md + skill receptie-factura-furnizor — maparea facturii, crearea NIR, reconciliere (pasul de DUPĂ acest skill).
knowledge/finante-facturare-contabilitate.md — conturi de stoc (371/301), TVA, impact P&L (referință).
knowledge/rapoarte-preturi.md — rapoarte de aprovizionare, food cost, analiză cost.