API -> "Copiaza tokenul" * set POLEO_CASA=DB9999999999 # NUI-ul casei (exemplul 2 ti-l arata) * * In PowerShell: * $env:POLEO_TOKEN=""; $env:POLEO_CASA=""; php poleo_demo.php --lista * * * ### DE CITIT INAINTE DE PRIMA LINIE DE COD IN PRODUCTIE ### * Bonul fiscal este foarte complicat de anulat si comporta constrangeri si consecinte. Orice * operatiune care lasa urma se trimite cu o CHEIE DE OPERATIUNE (`Idempotency-Key`) STABILA - * numarul comenzii tale, nu ceva generat la fiecare incercare. Exemplul 31 arata asta pe viu. * Nu trimite "probe" catre casa de marcat. Daca vrei sa verifici legatura, cere un raport X * (Atentie! NU Z). */ require __DIR__ . '/PoleoFiscal.php'; $poleo = PoleoFiscal::dinMediu(); $CASA = $poleo->casa(); // == afisare ====== function titlu($text) { echo "\n" . str_repeat('-', 74) . "\n" . $text . "\n" . str_repeat('-', 74) . "\n"; } function arata($eticheta, $obiect) { echo "\n" . $eticheta . ":\n"; echo json_encode($obiect, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; } function nota($text) { foreach (explode("\n", trim($text)) as $linie) { echo ' ' . trim($linie) . "\n"; } } /** * Cheie de operatiune pentru exemple. ATENTIE, aici este o ALTA cheie la fiecare rulare (are ora * in ea) - altfel al doilea rulaj al demonstratiei n-ar mai tipari nimic, ti-ar intoarce * rezultatul primului si ai crede ca s-a stricat ceva. IN PROGRAMUL TAU cheia trebuie sa fie * STABILA pentru aceeasi vanzare, ca reincercarea sa nu tipareasca al doilea bon. */ function cheie_demo($nume) { return sprintf('demo-%s-%d', $nume, (int) (microtime(true) * 1000)); } /** ruleaza ceva care poate fi refuzat, si arata refuzul in loc sa opreasca demonstratia */ function incearca($eticheta, callable $ce) { try { arata($eticheta, $ce()); } catch (EroareApi $e) { printf("\n%s -> HTTP %s (%s): %s\n", $eticheta, $e->cod, $e->codEroare ?: '-', $e->mesaj); } } // == 1-4: legatura si ce stie statia ====== function ex1_conectare(PoleoFiscal $p) { titlu('1. Conectarea la API'); arata('GET /health (singura cale care nu cere token)', $p->sanatate()); $metode = $p->metode(); $lista = isset($metode['methods']) ? $metode['methods'] : []; printf("\nMetodele pe care le stie ACEASTA statie (%d):\n", count($lista)); foreach ($lista as $m) { printf("%-6s %-28s %s\n", $m['method'] ?? '', $m['path'] ?? '', $m['about'] ?? ''); } nota('Cere /methods la pornirea integrarii tale, nu presupune. O statie mai veche poate sa nu aiba ultimele metode, iar asa afli din cod, nu dintr-o eroare la client.'); } function ex2_casele(PoleoFiscal $p) { titlu('2. Ce case sunt configurate (si de unde iei NUI-ul)'); arata('GET /cases', $p->casele()); nota('NUI-ul este identificatorul casei si nu se schimba pe toata durata vietii aparatului. Denumirea si id-ul din baza se pot schimba; NUI-ul, nu. Trimite-l de la inceput.'); } function ex3_stare(PoleoFiscal $p) { titlu('3. Starea casei, intrebata acum'); if ($p->casa() === '') { echo "(pune POLEO_CASA ca sa interoghezi o casa anume)\n"; } arata('GET /cases//status', $p->stare()); nota('Vorbeste chiar cu aparatul, deci intreaba la intervale rezonabile, nu in bucla si nu la fiecare incarcare de pagina. Starea monitorizarii si ziua fiscala vin din /problems.'); } function ex4_config(PoleoFiscal $p) { titlu('4. Cotele de TVA si metodele de incasare, citite din casa'); $r = $p->config(); arata('GET /cases//config', $r); $disp = $r['case']['device'] ?? []; echo "\nCote programate in aparat:\n"; foreach (($disp['vat'] ?? []) as $v) { printf("grupa %-3s = %s%%\n", $v['group'] ?? '?', $v['rate'] ?? '?'); } echo "Sloturi de incasare:\n"; foreach (($disp['payments'] ?? []) as $pl) { printf("index %-3s = %s\n", $pl['index'] ?? '?', $pl['name'] ?? '?'); } nota('Astea sunt adevarul. Nu presupune ca 21% este mereu grupa A sau ca 0 = NUMERAR, pentru ca vei altera datele fiscal-contabile.'); } // == 5-16: bonuri ====== function ex5_bon_numerar(PoleoFiscal $p) { titlu('5. Bon fiscal platit cu NUMERAR'); arata('POST /receipts', $p->bon([ 'items' => [['name' => 'APA PLATA 0.5L', 'price_val' => 3.50, 'qty' => 2, 'unit' => 'buc', 'vat_rate' => 21]], 'payments' => [$p->plata('cash', 7.00)], ], cheie_demo('numerar'))); nota('`receipt_no` este numarul bonului tiparit. Pune-l in evidenta ta - este legatura dintre vanzarea din programul tau si hartia pe care o are clientul.'); } function ex6_citeste_bonul(PoleoFiscal $p) { titlu('6. Citirea raspunsului: ultimul bon din evidenta'); $lista = $p->bonuri(1); arata('GET /receipts?limit=1', $lista); $bonuri = $lista['receipts'] ?? []; if ($bonuri) { arata('GET /receipts/' . $bonuri[0]['id'] . ' (cu liniile lui)', $p->bonul($bonuri[0]['id'])); } nota('Lista este pentru verificare ulterioara: reconciliere de sfarsit de zi, sau aflarea a ce s-a intamplat cu un bon intrat in coada cat timp casa era deconectata.'); } function ex7_bon_card(PoleoFiscal $p) { titlu('7. Bon fiscal platit cu CARDUL'); arata('POST /receipts', $p->bon([ 'items' => [['name' => 'CAFEA LA FILTRU', 'price_val' => 12.00, 'qty' => 1, 'unit' => 'buc', 'vat_rate' => 21]], 'payments' => [$p->plata('card', 12.00)], ], cheie_demo('card'))); nota('Asta TIPARESTE bonul; nu cere banii de pe card. Pentru terminalul bancar este /pos.'); } function ex8_plati_card(PoleoFiscal $p) { titlu('8. Citirea raspunsului pentru platile cu cardul'); arata('GET /card-payments?limit=5', $p->platiCard(5)); nota('Pastreaza `auth` si `rrn` - cu ele reconciliezi cu extrasul bancii. Plata INCERTA este cazul care conteaza: nu o relua automat, cardul poate fi deja debitat.'); } function ex9_bon_adaos(PoleoFiscal $p) { titlu('9. Bon cu ADAOS (majorare)'); arata('POST /receipts', $p->bon([ 'items' => [[ 'name' => 'LIVRARE URGENTA', 'price_val' => 20.00, 'qty' => 1, 'vat_rate' => 21, 'adjust_kind' => 'sur_pct', 'adjust_value' => 15, ]], 'payments' => [$p->plata('cash', 23.00)], ], cheie_demo('adaos'))); nota('`adjust_kind`: sur_val (lei) sau sur_pct (procent). `payments.amount` este totalul DUPA ajustare: 20 + 15% = 23,00.'); } function ex10_bon_reducere(PoleoFiscal $p) { titlu('10. Bon cu REDUCERE'); echo "\n--- a) CORECT: reducerea individualizata PE PRODUSUL redus ---\n"; arata('POST /receipts', $p->bon([ 'items' => [ ['name' => 'VIN ROSU 0.75L', 'price_val' => 45.00, 'qty' => 1, 'vat_rate' => 21, 'adjust_kind' => 'dis_pct', 'adjust_value' => 10], ['name' => 'PAINE DE CASA', 'price_val' => 8.00, 'qty' => 1, 'vat_rate' => 11], ], 'payments' => [$p->plata('cash', 48.50)], ], cheie_demo('reducere'))); nota('Individualizeaza EXPRES produsul redus, fara exceptie. Are implicatii nu doar fiscal-contabile, ci si juridice: clientul trebuie sa vada din ce s-a compus suma. Reducerea pe tot bonul se sparge pe cote si mosteneste cota fiecarei linii.'); } function ex11_plati_multiple(PoleoFiscal $p) { titlu('11. Bon cu DOUA metode de plata'); arata('POST /receipts', $p->bon([ 'items' => [['name' => 'MENIU ZILEI', 'price_val' => 40.00, 'qty' => 1, 'vat_rate' => 11]], 'payments' => [$p->plata('cash', 15.00), $p->plata('card', 25.00)], ], cheie_demo('mixt'))); nota('Suma platilor trebuie sa dea exact totalul bonului. Casa refuza altfel, si bine face.'); } function ex12_cote_multiple(PoleoFiscal $p) { titlu('12. Bon cu mai multe cote de TVA'); arata('POST /receipts', $p->bon([ 'items' => [ ['name' => 'PAINE', 'price_val' => 5.00, 'qty' => 1, 'vat_rate' => 11], ['name' => 'DETERGENT', 'price_val' => 24.00, 'qty' => 1, 'vat_rate' => 21], ['name' => 'AMBALAJ SGR', 'price_val' => 0.50, 'qty' => 1, 'vat_rate' => 0], ], 'payments' => [$p->plata('cash', 29.50)], ], cheie_demo('cote'))); nota('Garantia SGR merge pe cota 0 - nu este o vanzare, este o garantie returnabila.'); } function ex13_bon_complet(PoleoFiscal $p) { titlu('13. Bon complet: mai multe articole, ajustari, doua plati'); arata('POST /receipts', $p->bon([ 'items' => [ ['name' => 'PIZZA CAPRICCIOSA', 'price_val' => 38.00, 'qty' => 2, 'unit' => 'buc', 'vat_rate' => 11], ['name' => 'APA MINERALA 0.5L', 'price_val' => 6.00, 'qty' => 2, 'unit' => 'buc', 'vat_rate' => 21], ['name' => 'TAXA LIVRARE', 'price_val' => 12.00, 'qty' => 1, 'vat_rate' => 21, 'adjust_kind' => 'dis_val', 'adjust_value' => 2.00], ], 'payments' => [$p->plata('card', 80.00), $p->plata('cash', 8.00)], ], cheie_demo('complet'))); nota('Asa arata un bon real. Verifica intotdeauna ca totalul platilor este cel calculat de tine DUPA ajustari - nu lasa casa sa te corecteze, ca nu o face.'); } function ex14_storno(PoleoFiscal $p) { titlu('14. Linie de STORNO in bonul deschis'); arata('POST /receipts', $p->bon([ 'items' => [ ['name' => 'BERE 0.5L', 'price_val' => 9.00, 'qty' => 2, 'vat_rate' => 21], ['name' => 'BERE 0.5L', 'price_val' => 9.00, 'qty' => 1, 'vat_rate' => 21, 'storno' => true], ], 'payments' => [$p->plata('cash', 9.00)], ], cheie_demo('storno'))); nota('Stornarea unei linii se face IN bonul care inca nu s-a inchis. Dupa ce bonul a iesit, nu mai este storno - este retur (exemplul 42).'); } function ex15_stil_coduri(PoleoFiscal $p) { titlu('15. Articole trimise pe COD, nu pe denumire'); arata('POST /receipts', $p->bon([ 'items' => [['code' => '1024', 'qty' => 3, 'vat_rate' => 21]], 'payments' => [$p->plata('cash', 30.00)], ], cheie_demo('cod'))); nota('Merge doar daca articolul EXISTA in nomenclatorul casei, cu pretul programat acolo. Vezi exemplul 20 pentru raportul de nomenclator.'); } function ex16_copie(PoleoFiscal $p) { titlu('16. Copia ultimului bon'); arata('POST /copy', $p->copie(cheie_demo('copie'))); nota('Copia se tipareste cu mentiunea de duplicat. Nu este un al doilea bon fiscal.'); } function ex17_void(PoleoFiscal $p) { titlu('17. Anularea bonului DESCHIS'); incearca('POST /void', function () use ($p) { return $p->anuleazaBonDeschis(cheie_demo('void')); }); nota('Doar bonul nefinalizat. Functioneaza si cu licenta expirata - este una dintre cele doua supape ale programului (cealalta este sertarul).'); } function ex18_probleme(PoleoFiscal $p) { titlu('18. Ce nu este in regula acum'); arata('GET /problems', $p->probleme()); nota('Asta pui in bara de stare a portalului: monitorizare, ziua fiscala si de cate ore este deschisa, hartie, legatura. Nu starea casei - aia vorbeste cu aparatul de fiecare data.'); } // == 19-30: rapoarte, numerar, ANAF, jurnal ====== function ex19_raport_x(PoleoFiscal $p) { titlu('19. Raport X (citire, fara sa inchida ziua)'); arata('POST /reports {kind: X}', $p->raport('X', [], cheie_demo('x'))); nota('X este singura "proba" acceptabila daca vrei sa verifici ca ai legatura cu aparatul.'); } function ex20_nomenclator(PoleoFiscal $p) { titlu('20. Raportul de nomenclator'); foreach (['nomenclator', 'plu', 'departments'] as $fel) { try { arata('POST /reports {kind: ' . $fel . '}', $p->raport($fel, [], cheie_demo('nom-' . $fel))); break; } catch (EroareApi $e) { printf("%s -> HTTP %s: %s\n", $fel, $e->cod, $e->mesaj); } } nota('Nu orice casa are toate felurile. Cere /methods (exemplul 1) ca sa stii ce accepta a ta.'); } function ex21_depunere(PoleoFiscal $p) { titlu('21. Depunere de numerar in sertar'); arata('POST /cash {kind: in}', $p->numerar('in', 200, 'Fond de rulaj', cheie_demo('dep'))); } function ex22_retragere(PoleoFiscal $p) { titlu('22. Retragere de numerar din sertar'); arata('POST /cash {kind: out}', $p->numerar('out', 150, 'Ridicare incasari', cheie_demo('retr'))); nota('Se tipareste pe casa. Deci: cheie de operatiune, ca la bonuri.'); } function ex23_sertar(PoleoFiscal $p) { titlu('23. Soldul de sertar'); arata('GET /drawer', $p->sertar()); } function ex24_totaluri(PoleoFiscal $p) { titlu('24. Totalurile zilei si cotele'); arata('GET /totals', $p->totaluri()); arata('GET /vat', $p->cote()); nota('Ziua fiscala incepe la ultimul raport Z, nu la ora 00:00. Doua zile calendaristice pot sta in aceeasi zi fiscala, si invers.'); } function ex25_anaf_stare(PoleoFiscal $p) { titlu('25. Starea raportarii ANAF'); arata('GET /anaf', $p->anaf()); } function ex26_raport_periodic(PoleoFiscal $p) { titlu('26. Raport periodic din memoria fiscala'); arata('POST /reports {kind: periodic}', $p->raport('periodic', [ 'from' => '01-08-2026', 'to' => '31-08-2026', 'detailed' => false, ], cheie_demo('periodic'))); nota('Datele se dau zi-luna-an, NU in ISO. Poate tipari zeci de secunde - vezi nota despre `max_execution_time` din PoleoFiscal.php.'); } function ex27_export_je(PoleoFiscal $p) { titlu('27. Export de jurnal electronic pentru ANAF'); arata('POST /anaf', $p->exportJe('01-08-2026', '31-08-2026', cheie_demo('je'))); } function ex28_lista_je(PoleoFiscal $p) { titlu('28. Salvarile de jurnal electronic'); $r = $p->je(3); arata('GET /je?limit=3', $r); $salvari = $r['exports'] ?? ($r['je'] ?? []); if ($salvari) { arata('GET /je/' . $salvari[0]['id'] . ' (fisierele salvarii)', $p->jeSalvare($salvari[0]['id'])); } } function ex29_descarca_je(PoleoFiscal $p) { titlu('29. Descarcarea unui fisier de jurnal (raspuns BINAR)'); $r = $p->je(1); $salvari = $r['exports'] ?? ($r['je'] ?? []); if (!$salvari) { echo "(nicio salvare de jurnal - ruleaza intai exemplul 27)\n"; return; } $id = $salvari[0]['id']; $fisiere = $p->jeSalvare($id)['files'] ?? []; if (!$fisiere) { echo "(salvarea nu are fisiere)\n"; return; } $nume = is_array($fisiere[0]) ? ($fisiere[0]['name'] ?? '') : $fisiere[0]; $octeti = $p->jeFisier($id, $nume); $unde = __DIR__ . DIRECTORY_SEPARATOR . $nume; file_put_contents($unde, $octeti); printf("%s: %d octeti -> %s\n", $nume, strlen($octeti), $unde); nota('Raspunsul este BINAR, nu JSON. Nu-l trece prin nicio functie de text si nu-l pune in json_decode - il scrii pe disc, sau il trimiti la browser cu antetele lui.'); } function ex30_raport_z(PoleoFiscal $p) { titlu('30. Raport Z (INCHIDE ZIUA FISCALA)'); $ziua = date('Y-m-d'); arata('POST /reports {kind: Z}', $p->raport('Z', [], 'z-' . $ziua)); nota('Cheia este ziua, nu ora: doua apasari in aceeasi zi nu dau doua rapoarte Z. Z-ul pune toate valorile pe 0. Nu-l pune pe un buton la indemana intr-un portal.'); } // == 31-32: idempotenta si erorile ====== function ex31_idempotenta(PoleoFiscal $p) { titlu('31. Cheia de operatiune, pe viu'); $cheie = cheie_demo('idem'); $corp = [ 'items' => [['name' => 'TEST IDEMPOTENTA', 'price_val' => 1.00, 'qty' => 1, 'vat_rate' => 21]], 'payments' => [$p->plata('cash', 1.00)], ]; arata('prima trimitere', $p->bon($corp, $cheie)); arata('A DOUA trimitere, cu ACEEASI cheie', $p->bon($corp, $cheie)); nota('Al doilea raspuns este acelasi ca primul, si NU a iesit al doilea bon. Asta te salveaza cand utilizatorul da F5 sau apasa de doua ori pe buton in portal.'); } function ex32_erori(PoleoFiscal $p) { titlu('32. Cum arata refuzurile, si pe ce te ramifici'); $cazuri = [ 'cerere fara articole' => function () use ($p) { return $p->bon(['items' => [], 'payments' => [$p->plata('cash', 1)]], cheie_demo('gol')); }, 'plata care nu acopera bonul' => function () use ($p) { return $p->bon([ 'items' => [['name' => 'X', 'price_val' => 10.00, 'qty' => 1, 'vat_rate' => 21]], 'payments' => [$p->plata('cash', 5.00)], ], cheie_demo('scurt')); }, 'raport care nu exista' => function () use ($p) { return $p->raport('nu-exista-asa-ceva', [], cheie_demo('rap')); }, ]; foreach ($cazuri as $ce => $f) { try { $f(); echo "\n" . $ce . ": a trecut (neasteptat)\n"; } catch (EroareApi $e) { printf("\n%-32s HTTP %-4s cod=%-16s %s\n", $ce, $e->cod, $e->codEroare ?: '-', $e->mesaj); } } nota('Ramifica-te pe `codEroare`, NU pe mesaj: codul este stabil, mesajul este pentru om si se poate schimba fara sa strice integrarea ta.'); } // == 33-41: sertar si terminalul de card ====== function ex33_sertar_deschide(PoleoFiscal $p) { titlu('33. Deschiderea sertarului'); incearca('POST /drawer', function () use ($p) { return $p->deschideSertar(cheie_demo('sertar')); }); nota('Necesita sertar legat electric la casa. Unele case nu-l pot gestiona, desi au port.'); } function ex34_pos_card(PoleoFiscal $p) { titlu('34. Plata la terminalul de card (POS bancar)'); incearca('POST /pos', function () use ($p) { return $p->pos(12.00, cheie_demo('pos')); }); nota('Asta CERE BANII de pe card; nu tipareste bonul. Intai /pos, si doar daca terminalul a aprobat, /receipts cu plata pe card. Programul nu vede niciodata PAN, CVV sau PIN.'); } function ex35_terminale(PoleoFiscal $p) { titlu('35. Terminalele de card configurate'); $r = $p->terminale(); arata('GET /terminals', $r); foreach (($r['terminals'] ?? []) as $t) { $poate = $t['can'] ?? []; printf( "%-22s %-16s retur: %-5s anulare: %-5s inchidere de zi: %s\n", $t['name'] ?? '-', $t['model'] ?? '-', var_export($poate['refund'] ?? null, true), var_export($poate['void'] ?? null, true), var_export($poate['settlement'] ?? null, true) ); } nota('Citeste `can` INAINTE sa promiti ceva clientului - se citeste din driver, nu de pe fir. Ce este `false` acolo nu se reincearca niciodata.'); } function ex36_licenta(PoleoFiscal $p) { titlu('36. Licenta fiecarei case'); arata('GET /license', $p->licenta()); nota('O licenta expirata opreste bonurile, rapoartele si miscarile de numerar. Afla inainte, nu in dimineata in care casa refuza.'); } function ex37_statie(PoleoFiscal $p) { titlu('37. Amprenta statiei, locatia si casele ei'); arata('GET /station', $p->statie()); nota('AMPRENTA este numele sub care te cunosc partenerii. Nu se schimba nici la actualizare, nici daca schimbi portul, IP-ul sau casa.'); } function ex38_restul_evidentei(PoleoFiscal $p) { titlu('38. Restul evidentei: numerar, rapoarte tiparite, declaratii, posta'); incearca('GET /cash?limit=3 (miscarile de numerar)', function () use ($p) { return $p->miscariNumerar(3); }); incearca('GET /reports?limit=3 (rapoartele tiparite)', function () use ($p) { return $p->rapoarte(3); }); incearca('GET /declarations?limit=3 (declaratiile de bon gresit)', function () use ($p) { return $p->declaratii(3); }); incearca('GET /emails?limit=3 (posta de iesire)', function () use ($p) { return $p->posta(3); }); $decl = $p->declaratii(1)['declarations'] ?? []; if ($decl) { $did = $decl[0]['id']; $pdf = $p->declaratieFisier($did); $unde = __DIR__ . DIRECTORY_SEPARATOR . 'declaratie-' . $did . '.pdf'; file_put_contents($unde, $pdf); printf("PDF-ul declaratiei %s: %d octeti -> %s\n", $did, strlen($pdf), $unde); } else { echo "(nicio declaratie de bon gresit - normal pe o casa fara incidente)\n"; } nota('Lista completa si mereu la zi o da chiar statia, la /methods - sau site-ul https://poleo.ro/docs/api'); } /* Retur, anulare si inchidere de zi pe terminal: cele trei nu exista pe orice terminal si nu sunt in orice contract cu banca. De aceea raspunsul lor are un caz in plus fata de "a mers / n-a mers": HTTP 501 cu `unsupported`, adica "aparatul asta nu are operatiunea". Este DEFINITIV.*/ function _card_operatie($nume, callable $ce) { try { arata($nume, $ce()); } catch (EroareApi $e) { if ($e->esteNesuportat()) { printf("\nNESUPORTAT de terminalul asta: %s\n", $e->mesaj); echo "Asta NU se reincearca - nici acum, nici maine. Solutia este alt terminal, alt\n"; echo "model, sau alt contract cu banca.\n"; if ($e->detalii) { arata('ce aparat a refuzat', $e->detalii); } } else { printf("\n%s -> HTTP %s (%s): %s\n", $nume, $e->cod, $e->codEroare ?: '-', $e->mesaj); } } } function ex39_retur_card(PoleoFiscal $p) { titlu('39. Retur pe card'); _card_operatie('POST /pos/refund', function () use ($p) { return $p->posRetur(25.00, cheie_demo('retur')); }); nota('Scoate bani REALI din contul comerciantului. Verifica `can.refund` la /terminals INAINTE, ca sa nu descoperi asta dupa ce clientul a predat marfa. Altceva decat exemplul 42: acolo este returul PE BON, aici returul PE TERMINAL.'); } function ex40_anulare_card(PoleoFiscal $p) { titlu('40. Anularea unei plati cu cardul'); _card_operatie('POST /pos/void', function () use ($p) { return $p->posAnulare('000123', 25.00, cheie_demo('anulare')); }); nota('Are nevoie de STAN SI de suma exacta, tocmai ca sa nu anulezi alta tranzactie. Pastreaza STAN-ul la fiecare plata, altfel nu mai ai cu ce anula.'); } function ex41_settlement(PoleoFiscal $p) { titlu('41. Inchiderea de zi a terminalului (settlement)'); _card_operatie('POST /pos/settlement', function () use ($p) { return $p->posInchidereZi(cheie_demo('settlement')); }); nota('NU are legatura cu raportul Z al casei - sunt doua zile diferite. Nu o facem noi automat: are efect asupra banilor, deci o cere omul.'); } // == 42-45: returul pe bon emis, documentele, firma, setarile ====== /** id-ul ultimului bon TIPARIT din evidenta - returul si declaratia lucreaza pe el */ function _ultimul_bon_tiparit(PoleoFiscal $p) { foreach (($p->bonuri(10)['receipts'] ?? []) as $b) { if (($b['status'] ?? '') === 'ok') { return $b['id']; } } return null; } function ex42_retur(PoleoFiscal $p) { titlu('42. Retur pe un bon deja emis'); $bid = _ultimul_bon_tiparit($p); if (!$bid) { echo "(niciun bon tiparit in evidenta - ruleaza intai exemplul 5)\n"; } else { incearca('POST /returns', function () use ($p, $bid) { return $p->retur($bid, 'produs defect, adus inapoi de client', 'demo'); }); } arata('GET /returns?limit=3', $p->retururi(3)); nota('NU se tipareste bon de storno: bonul initial ramane valabil. Ce iese este retragerea de numerar si procesul-verbal. Acelasi bon nu poate fi returnat de doua ori peste suma lui. `acknowledged` inseamna ca OMUL a decis - nu-l pune true dintr-un camp ascuns.'); } function ex43_declaratie(PoleoFiscal $p) { titlu('43. Declaratia de bon gresit'); arata('GET /declaration-reasons', $p->motiveDeclaratie()); $bid = _ultimul_bon_tiparit($p); $motive = array_slice(array_map( function ($m) { return $m['key'] ?? null; }, $p->motiveDeclaratie()['reasons'] ?? [] ), 0, 1); if ($bid && $motive) { incearca('POST /declarations', function () use ($p, $bid, $motive) { return $p->declaratie($bid, $motive, 'demo'); }); } nota('Motivele se iau din /declaration-reasons, nu se inventeaza. PDF-ul se descarca separat, ca raspuns binar (exemplul 38).'); } function ex44_firma(PoleoFiscal $p) { titlu('44. Datele firmei, asa cum le stie statia'); $r = $p->firma(); arata('GET /company', $r); // DOUA LIPSURI DIFERITE, doua mesaje. `missing` = documentul iese incomplet. `email_missing` = // documentele sunt in regula, dar mesajele pleaca fara adresa de raspuns. if (! empty($r['email_missing'])) { echo "\nLIPSESTE ADRESA DE RASPUNS a firmei (campul `email`).\n", "Fara ea, cine primeste jurnalul electronic apasa «Raspunde» si scrie in gol.\n"; } nota('Sunt datele cu care se scriu documentele (procese-verbale, declaratii). Se configureaza PE CASA, nu global. Campul `email` face altceva: NU apare pe niciun act, ci e adresa de raspuns pusa in mesajele trimise de aplicatie, ca sa ajunga la FIRMA, nu la noi.'); } function ex45_setari(PoleoFiscal $p) { titlu('45. Setarile statiei: citire si scriere'); $r = $p->setari(); arata('GET /settings', $r); echo "\nCe se poate scrie prin API:\n"; foreach (($r['settings'] ?? []) as $s) { if (!empty($s['writable'])) { printf(" %-28s %s\n", $s['key'] ?? '', $s['secret'] ?? false ? '(secret - nu iese la citire)' : ''); } } nota('Parolele NU ies niciodata la citire: vin cu `value: null` si `secret: true`. Deci nu face un formular care "reincarca" parola existenta - n-o vei primi.'); echo "\n--- scrierea: una buna si una refuzata, in aceeasi cerere ---\n"; incearca('POST /settings', function () use ($p) { return $p->scrieSetari(['bon.sertar_mereu' => false, 'api.port' => '1', 'sigiliu.on' => false]); }); nota('`sigiliu.on` este REFUZAT intotdeauna, si nu e o scapare de drepturi: sigiliul semneaza fiecare bon, raport si operatiune de numerar, iar o semnatura care se poate stinge nu dovedeste nimic. Nu se opreste nici din aplicatie. Backupul, in schimb, se configureaza liber: `backup.parti` (config, evidenta, fisiere - despartite prin virgula) si `backup.keep` (cate arhive se pastreaza).'); echo "\n--- setarile CASEI, nu ale statiei ---\n"; if ($p->casa() !== '') { $r2 = $p->setari($p->casa()); foreach (array_slice(array_filter($r2['settings'] ?? [], function ($s) { return ($s['scope'] ?? '') === 'casa'; }), 0, 5) as $s) { printf(" %-28s own=%-5s = %s\n", $s['key'] ?? '', var_export($s['own'] ?? false, true), $s['value'] ?? ''); } incearca('POST /settings (pe casa)', function () use ($p) { return $p->scrieSetari(['num.plafon' => 40000], $p->casa()); }); } nota('Setarile cu `scope: "casa"` sunt ale APARATULUI si se tin pe SERIA lui, ca sa urmeze aparatul si nu fisa din aplicatie. Le citesti cu ?nui=SERIA si le scrii trimitand `nui`. Scrise fara serie, cand casele au deja valorile lor, sunt REFUZATE - altfel ar ramane pe cheia comuna, pe care nicio casa n-o mai citeste. EMAILUL nu este al casei: contul, calea (`email.cale`: "releu" prin serverul nostru, fara parole pe calculatorul clientului, sau "smtp" cu contul tau), ora si destinatarul sunt ale statiei si se scriu o singura data.'); nota('Tabul API in intregime este blocat DINADINS: API-ul nu isi poate schimba propriile conditii de existenta. Omul il porneste si il accepta din aplicatie, si abia dupa aceea este autorizat. Restul refuzurilor vin cu motiv, unul cate unul.'); // SFTP: acelasi drum, doar ca parola pleaca o singura data si nu se mai citeste inapoi. // arata('POST /settings (SFTP)', $p->scrieSetari([ // 'sftp.on' => true, 'sftp.host' => 'sftp.exemplu.ro', 'sftp.port' => '22', // 'sftp.user' => 'poleo', 'sftp.pass' => 'PAROLA', 'sftp.dir' => '/bonuri', // ])); } // == catalogul si rularea ====== function ex46_protectie(PoleoFiscal $p) { titlu('46. Protectia atasamentelor trimise pe email'); $r = $p->setari(); $nivel = null; foreach (($r['settings'] ?? []) as $s) { if (($s['key'] ?? '') === 'email.protectie.nivel') { $nivel = $s; break; } } if ($nivel === null) { echo "Statia asta nu are protectia atasamentelor (versiune mai veche de 1.2.16).\n"; return; } $inCuvinte = [ 'fara' => 'pleaca asa cum sunt, oricine ajunge la cutia postala le poate deschide', 'parola' => 'pleaca intr-o arhiva ZIP cifrata AES-256, care cere o parola', 'certificat' => 'pleaca invelite catre certificatul destinatarului (S/MIME, fisier .p7m)', 'certificat_parola' => 'pleaca in arhiva cu parola, invelita apoi catre certificat', ]; $val = (string) ($nivel['value'] ?? 'fara'); printf("treapta : %s\n", $val); printf("adica : fisierele %s\n", $inCuvinte[$val] ?? '(treapta necunoscuta)'); printf("se scrie? : %s\n", var_export($nivel['writable'] ?? false, true)); if (!empty($nivel['reason'])) { printf("de ce nu : %s\n", $nivel['reason']); } echo "\n--- incerc sa o cobor de la distanta ---\n"; incearca('POST /settings', function () use ($p) { return $p->scrieSetari(['email.protectie.nivel' => 'fara']); }); $scapate = []; foreach (($r['settings'] ?? []) as $s) { $k = (string) ($s['key'] ?? ''); if (strpos($k, 'email.protectie.') === 0 && $k !== 'email.protectie.nivel') { $scapate[] = $k; } } printf("\nalte chei de protectie vizibile: %s\n", $scapate ? implode(', ', $scapate) : 'niciuna'); nota('De ce te intereseaza pe tine, integratorul: daca treapta NU e "fara", omul care primeste raportul are nevoie de ceva in plus ca sa-l deschida - parola, certificatul lui, sau amandoua. Un fisier .p7m primit fara explicatie ajunge la cosul de gunoi, asa ca merita sa spui in interfata ta ce urmeaza sa primeasca destinatarul. Treapta se CITESTE tocmai pentru asta, dar NU se poate SCRIE de la distanta: cine ar cobori-o ar scoate fisierele in clar fara ca omul din magazin sa afle. PAROLA ARHIVEI SI CHEILE nu apar nicaieri in raspuns, nici macar ca `secret: true` cu `value: null` - pur si simplu nu sunt in catalog. Cifrarea se face pe calculatorul clientului, INAINTE ca fisierele sa plece, deci nici serverele Poleo nu le pot citi cand mesajul trece pe releu.'); } $EXEMPLE = [ 1 => ['Conectarea la API', 'ex1_conectare', false], 2 => ['Casele configurate', 'ex2_casele', false], 3 => ['Starea casei', 'ex3_stare', false], 4 => ['Cotele de TVA si incasarile', 'ex4_config', false], 5 => ['Bon platit cu numerar', 'ex5_bon_numerar', false], 6 => ['Citirea unui bon din evidenta', 'ex6_citeste_bonul', false], 7 => ['Bon platit cu cardul', 'ex7_bon_card', false], 8 => ['Platile cu cardul', 'ex8_plati_card', false], 9 => ['Bon cu adaos', 'ex9_bon_adaos', false], 10 => ['Bon cu reducere', 'ex10_bon_reducere', false], 11 => ['Doua metode de plata', 'ex11_plati_multiple', false], 12 => ['Mai multe cote de TVA', 'ex12_cote_multiple', false], 13 => ['Bon complet', 'ex13_bon_complet', false], 14 => ['Storno in bonul deschis', 'ex14_storno', false], 15 => ['Articole pe cod', 'ex15_stil_coduri', false], 16 => ['Copia ultimului bon', 'ex16_copie', false], 17 => ['Anularea bonului deschis', 'ex17_void', false], 18 => ['Ce nu este in regula acum', 'ex18_probleme', false], 19 => ['Raport X', 'ex19_raport_x', false], 20 => ['Raport de nomenclator', 'ex20_nomenclator', false], 21 => ['Depunere de numerar', 'ex21_depunere', false], 22 => ['Retragere de numerar', 'ex22_retragere', false], 23 => ['Soldul de sertar', 'ex23_sertar', false], 24 => ['Totalurile zilei', 'ex24_totaluri', false], 25 => ['Starea ANAF', 'ex25_anaf_stare', false], 26 => ['Raport periodic', 'ex26_raport_periodic', false], 27 => ['Export de jurnal electronic', 'ex27_export_je', false], 28 => ['Salvarile de jurnal', 'ex28_lista_je', false], 29 => ['Descarcarea unui fisier de jurnal', 'ex29_descarca_je', false], 30 => ['Raport Z (INCHIDE ZIUA)', 'ex30_raport_z', true], 31 => ['Cheia de operatiune, pe viu', 'ex31_idempotenta', false], 32 => ['Cum arata refuzurile', 'ex32_erori', false], 33 => ['Deschiderea sertarului', 'ex33_sertar_deschide', false], 34 => ['Plata la terminalul de card', 'ex34_pos_card', false], 35 => ['Terminalele configurate', 'ex35_terminale', false], 36 => ['Licenta caselor', 'ex36_licenta', false], 37 => ['Amprenta statiei', 'ex37_statie', false], 38 => ['Restul evidentei', 'ex38_restul_evidentei', false], 39 => ['Retur pe card', 'ex39_retur_card', true], 40 => ['Anularea unei plati cu cardul', 'ex40_anulare_card', true], 41 => ['Inchiderea de zi a terminalului', 'ex41_settlement', true], 42 => ['Retur pe un bon emis', 'ex42_retur', true], 43 => ['Declaratia de bon gresit', 'ex43_declaratie', false], 44 => ['Datele firmei', 'ex44_firma', false], 45 => ['Setarile statiei', 'ex45_setari', false], 46 => ['Protectia atasamentelor', 'ex46_protectie', false], ]; function lista(array $EXEMPLE) { echo "\nExemple disponibile:\n"; foreach ($EXEMPLE as $nr => $e) { printf("%3d. %-42s %s\n", $nr, $e[0], $e[2] ? ' [periculos - cere --da]' : ''); } echo "\nPericulos = lasa urma pe care nu o poti sterge: inchide ziua fiscala, misca bani reali.\n"; } // rulare doar din linia de comanda; daca fisierul ajunge sub un server web, nu executa nimic if (PHP_SAPI !== 'cli') { http_response_code(403); exit("poleo_demo.php se ruleaza din linia de comanda, nu din browser.\n"); } $argumente = array_slice($argv, 1); $da = in_array('--da', $argumente, true); $argumente = array_values(array_filter($argumente, function ($a) { return $a !== '--da'; })); if (!$argumente || in_array('--lista', $argumente, true)) { lista($EXEMPLE); exit(0); } $deRulat = in_array('toate', $argumente, true) ? array_keys($EXEMPLE) : array_map('intval', $argumente); foreach ($deRulat as $nr) { if (!isset($EXEMPLE[$nr])) { printf("Nu exista exemplul %s. Ruleaza --lista.\n", $nr); continue; } list($nume, $functie, $periculos) = $EXEMPLE[$nr]; if ($periculos && !$da) { printf("%3d. %-42s SARIT (periculos; adauga --da)\n", $nr, $nume); continue; } try { $functie($poleo); } catch (EroareApi $e) { printf("\nExemplul %d s-a oprit: HTTP %s (%s) - %s\n", $nr, $e->cod, $e->codEroare ?: '-', $e->mesaj); } }