smartbill-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +211 -0
  3. data/CHANGELOG.md +18 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +265 -0
  6. data/Rakefile +22 -0
  7. data/Steepfile +6 -0
  8. data/docs/openapi.json +9741 -0
  9. data/examples/create_estimate_sync.rb +53 -0
  10. data/examples/create_invoice_eur_product_in_ron.rb +100 -0
  11. data/examples/create_invoice_sync.rb +57 -0
  12. data/examples/create_payment_sync.rb +36 -0
  13. data/examples/fiscal_receipt_sync.rb +50 -0
  14. data/examples/invoice_lifecycle_sync.rb +43 -0
  15. data/examples/list_series_sync.rb +27 -0
  16. data/examples/send_email_sync.rb +40 -0
  17. data/examples/taxes_and_stocks_sync.rb +48 -0
  18. data/lib/smartbill/sdk/api_error.rb +27 -0
  19. data/lib/smartbill/sdk/auth_error.rb +8 -0
  20. data/lib/smartbill/sdk/client.rb +68 -0
  21. data/lib/smartbill/sdk/contracts/base.rb +33 -0
  22. data/lib/smartbill/sdk/contracts/email_contract.rb +29 -0
  23. data/lib/smartbill/sdk/contracts/estimate_contract.rb +18 -0
  24. data/lib/smartbill/sdk/contracts/invoice_contract.rb +29 -0
  25. data/lib/smartbill/sdk/contracts/invoice_payment_contract.rb +18 -0
  26. data/lib/smartbill/sdk/contracts/invoice_ref_contract.rb +19 -0
  27. data/lib/smartbill/sdk/contracts/payment_contract.rb +37 -0
  28. data/lib/smartbill/sdk/contracts/storno_contract.rb +17 -0
  29. data/lib/smartbill/sdk/contracts.rb +19 -0
  30. data/lib/smartbill/sdk/error.rb +8 -0
  31. data/lib/smartbill/sdk/models/base_response.rb +16 -0
  32. data/lib/smartbill/sdk/models/client.rb +26 -0
  33. data/lib/smartbill/sdk/models/discount_type.rb +13 -0
  34. data/lib/smartbill/sdk/models/document_type.rb +13 -0
  35. data/lib/smartbill/sdk/models/email_document.rb +23 -0
  36. data/lib/smartbill/sdk/models/email_response.rb +12 -0
  37. data/lib/smartbill/sdk/models/email_status.rb +13 -0
  38. data/lib/smartbill/sdk/models/estimate.rb +32 -0
  39. data/lib/smartbill/sdk/models/fiscal_receipt_response.rb +16 -0
  40. data/lib/smartbill/sdk/models/invoice.rb +44 -0
  41. data/lib/smartbill/sdk/models/invoice_create_response.rb +10 -0
  42. data/lib/smartbill/sdk/models/invoice_payment.rb +15 -0
  43. data/lib/smartbill/sdk/models/invoice_ref.rb +24 -0
  44. data/lib/smartbill/sdk/models/payment.rb +49 -0
  45. data/lib/smartbill/sdk/models/payment_status_response.rb +19 -0
  46. data/lib/smartbill/sdk/models/payment_type.rb +22 -0
  47. data/lib/smartbill/sdk/models/product.rb +38 -0
  48. data/lib/smartbill/sdk/models/proforma_invoices_response.rb +13 -0
  49. data/lib/smartbill/sdk/models/series.rb +14 -0
  50. data/lib/smartbill/sdk/models/series_list_response.rb +14 -0
  51. data/lib/smartbill/sdk/models/stock_list.rb +13 -0
  52. data/lib/smartbill/sdk/models/stock_product.rb +15 -0
  53. data/lib/smartbill/sdk/models/stock_warehouse.rb +13 -0
  54. data/lib/smartbill/sdk/models/stocks_response.rb +14 -0
  55. data/lib/smartbill/sdk/models/storno_request.rb +17 -0
  56. data/lib/smartbill/sdk/models/storno_response.rb +14 -0
  57. data/lib/smartbill/sdk/models/struct.rb +102 -0
  58. data/lib/smartbill/sdk/models/tax.rb +14 -0
  59. data/lib/smartbill/sdk/models/taxes_response.rb +14 -0
  60. data/lib/smartbill/sdk/models.rb +17 -0
  61. data/lib/smartbill/sdk/net_http_adapter.rb +62 -0
  62. data/lib/smartbill/sdk/rate_limit_error.rb +9 -0
  63. data/lib/smartbill/sdk/response.rb +12 -0
  64. data/lib/smartbill/sdk/services/base_service.rb +39 -0
  65. data/lib/smartbill/sdk/services/configuration_service.rb +29 -0
  66. data/lib/smartbill/sdk/services/email_service.rb +18 -0
  67. data/lib/smartbill/sdk/services/estimates_service.rb +60 -0
  68. data/lib/smartbill/sdk/services/invoices_service.rb +69 -0
  69. data/lib/smartbill/sdk/services/payments_service.rb +50 -0
  70. data/lib/smartbill/sdk/services/stocks_service.rb +21 -0
  71. data/lib/smartbill/sdk/services.rb +30 -0
  72. data/lib/smartbill/sdk/transport/rate_limiter.rb +49 -0
  73. data/lib/smartbill/sdk/transport/request.rb +17 -0
  74. data/lib/smartbill/sdk/transport.rb +162 -0
  75. data/lib/smartbill/sdk/transport_error.rb +8 -0
  76. data/lib/smartbill/sdk/types.rb +22 -0
  77. data/lib/smartbill/sdk/validation_error.rb +8 -0
  78. data/lib/smartbill/sdk/version.rb +7 -0
  79. data/lib/smartbill/sdk.rb +31 -0
  80. data/sig/smartbill/sdk.rbs +661 -0
  81. data/skills/README.md +35 -0
  82. data/skills/smartbill-email/SKILL.md +120 -0
  83. data/skills/smartbill-invoices/SKILL.md +178 -0
  84. data/skills/smartbill-payments/SKILL.md +194 -0
  85. metadata +214 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: emiterea unei proforme (sincron).
4
+ #
5
+ # Proforma se creează cu Estimate și se trimite prin
6
+ # client.estimates.create(estimate) (POST /estimate). Structura este foarte
7
+ # asemănătoare cu o factură: client, serie, produse.
8
+ #
9
+ # Documentul poate fi convertit ulterior în factură; starea conversiei se
10
+ # verifică cu client.estimates.invoices_status(...).
11
+
12
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
13
+ require "smartbill/sdk"
14
+
15
+ def main
16
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
17
+ estimate = Smartbill::Sdk::Models::Estimate.new(
18
+ company_vat_code: "RO12345678",
19
+ client: Smartbill::Sdk::Models::Client.new(
20
+ name: "Intelligent IT",
21
+ vat_code: "RO12345678",
22
+ email: "office@intelligent.ro"
23
+ ),
24
+ series_name: "PFC",
25
+ is_draft: false,
26
+ products: [
27
+ Smartbill::Sdk::Models::Product.new(
28
+ name: "Serviciu dezvoltare",
29
+ measuring_unit_name: "ore",
30
+ currency: "RON",
31
+ quantity: 10,
32
+ price: 150,
33
+ is_tax_included: false,
34
+ tax_name: "Normala",
35
+ tax_percentage: 19
36
+ )
37
+ ]
38
+ )
39
+
40
+ resp = client.estimates.create(estimate)
41
+ puts "Proforma emisa: seria #{resp.series}, numarul #{resp.number}"
42
+
43
+ # Verificăm dacă proforma a fost deja convertită în factură.
44
+ status = client.estimates.invoices_status("RO12345678", resp.series, resp.number)
45
+ if status.are_invoices_created
46
+ status.invoices.each { |inv| puts " Factura generata: #{inv.series_name} #{inv.number}" }
47
+ else
48
+ puts " Proforma nu a fost inca convertita in factura."
49
+ end
50
+ end
51
+ end
52
+
53
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: emiterea unei facturi în RON cu un produs cu preț de referință în
4
+ # EUR (sincron).
5
+ #
6
+ # Acest script arată cum se emite o factură a cărei monedă este RON, dar al
7
+ # cărei produs are prețul de referință în EUR. SmartBill convertește
8
+ # automat folosind cursul BNR din data emiterii (când nu se specifică
9
+ # `exchange_rate` pe produs), sau se poate da un curs explicit.
10
+ #
11
+ # Tiparul corespunde definiției `exempluFacturaPreturiValuta` din
12
+ # `docs/openapi.json`: produsul are `currency: "EUR"`, iar factura are
13
+ # `currency: "RON"`.
14
+ #
15
+ # `is_draft: true` salvează factura ca ciornă (fără număr, neemitcă către
16
+ # ANAF) — sigur pentru teste pe cont real. Pentru emitere finală, folosește
17
+ # `is_draft: false`.
18
+
19
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
20
+ require "smartbill/sdk"
21
+
22
+ # TODO: înlocuiește cu datele tale.
23
+ USERNAME = "you@example.com"
24
+ TOKEN = "YOUR_TOKEN"
25
+ COMPANY_VAT_CODE = "52859275" # CIF-ul firmei tale (emitentul)
26
+ CLIENT_VAT_CODE = "RO46548525" # CIF-ul clientului
27
+ SERIES_NAME = "ABC"
28
+
29
+ # Cursul EUR->RON. Lasă-l `nil` pentru a lăsa SmartBill să folosească cursul
30
+ # BNR din data emiterii. Dacă API-ul cere un curs explicit, setează-l aici,
31
+ # de ex. `EXCHANGE_RATE = 5.05`.
32
+ EXCHANGE_RATE = nil
33
+
34
+ def build_product(name:, price_eur:, quantity:, exchange_rate:)
35
+ attrs = {
36
+ name: name,
37
+ is_service: true,
38
+ measuring_unit_name: "ore",
39
+ currency: "EUR", # moneda prețului de referință
40
+ price: price_eur, # preț unitar în EUR (fără TVA)
41
+ quantity: quantity,
42
+ is_tax_included: false, # B2B: preț fără TVA
43
+ tax_name: "Normala",
44
+ tax_percentage: 19
45
+ }
46
+ attrs[:exchange_rate] = exchange_rate unless exchange_rate.nil?
47
+ Smartbill::Sdk::Models::Product.new(**attrs)
48
+ end
49
+
50
+ def build_invoice
51
+ product = build_product(
52
+ name: "Servicii de dezvoltare software",
53
+ price_eur: 31,
54
+ quantity: 168,
55
+ exchange_rate: EXCHANGE_RATE
56
+ )
57
+
58
+ Smartbill::Sdk::Models::Invoice.new(
59
+ company_vat_code: COMPANY_VAT_CODE,
60
+ client: Smartbill::Sdk::Models::Client.new(
61
+ name: "Intelligent IT",
62
+ vat_code: CLIENT_VAT_CODE,
63
+ city: "Sibiu",
64
+ county: "Sibiu",
65
+ country: "Romania",
66
+ address: "str. Sperantei, nr. 5",
67
+ email: "office@intelligent.ro"
68
+ ),
69
+ series_name: SERIES_NAME,
70
+ is_draft: true, # ciornă: salvată, nefinalizată, fără număr
71
+ issue_date: Time.now.strftime("%Y-%m-%d"),
72
+ due_date: (Time.now + (86_400 * 15)).strftime("%Y-%m-%d"),
73
+ currency: "RON", # moneda documentului
74
+ mentions: "Nota Comanda Nr. 1 din 11.05.2026, Contract WP271",
75
+ products: [product]
76
+ )
77
+ end
78
+
79
+ def main
80
+ Smartbill::Sdk::Client.new(username: USERNAME, token: TOKEN).with_client do |client|
81
+ invoice = build_invoice
82
+ resp = client.invoices.create(invoice)
83
+
84
+ if resp.number && !resp.number.empty?
85
+ puts "Factura finalizată: seria #{resp.series}, numărul #{resp.number}"
86
+ else
87
+ # La ciornă, `number` este gol — factura a fost doar salvată.
88
+ puts "Factura salvată în ciornă (seria #{resp.series})."
89
+ end
90
+ puts "Mesaj API: #{resp.message}" if resp.message
91
+ end
92
+ rescue Smartbill::Sdk::AuthError => e
93
+ warn "Eroare autentificare (401): #{e.message}"
94
+ rescue Smartbill::Sdk::APIError => e
95
+ warn "Eroare API [#{e.status_code}]: #{e.error_text}"
96
+ rescue Smartbill::Sdk::ValidationError => e
97
+ warn "Eroare validare: #{e.message}"
98
+ end
99
+
100
+ main
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: emiterea unei facturi simple (sincron).
4
+ #
5
+ # Acest script arată cum se creează și se emite o factură nouă folosind
6
+ # clientul sincron Smartbill::Sdk::Client. Factura conține un singur produs
7
+ # cu TVA inclus (cota redusă) și este emisă direct (is_draft: false).
8
+ #
9
+ # Câmpurile din modele folosesc snake_case în Ruby, dar sunt serializate
10
+ # automat în camelCase către API (ex. company_vat_code devine companyVatCode).
11
+
12
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
13
+ require "smartbill/sdk"
14
+
15
+ def main
16
+ # 1. Autentificare (HTTP Basic Auth: username:token).
17
+ client = Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN")
18
+
19
+ # 2. Construirea facturii.
20
+ invoice = Smartbill::Sdk::Models::Invoice.new(
21
+ company_vat_code: "RO12345678",
22
+ client: Smartbill::Sdk::Models::Client.new(
23
+ name: "Intelligent IT",
24
+ vat_code: "RO12345678",
25
+ address: "str. Sperantei, nr. 5",
26
+ city: "Sibiu",
27
+ county: "Sibiu",
28
+ country: "Romania",
29
+ email: "office@intelligent.ro"
30
+ ),
31
+ series_name: "FCT",
32
+ is_draft: false,
33
+ issue_date: "2024-05-01",
34
+ due_date: "2024-05-15",
35
+ products: [
36
+ Smartbill::Sdk::Models::Product.new(
37
+ name: "Produs 1",
38
+ code: "ccd1",
39
+ measuring_unit_name: "buc",
40
+ currency: "RON",
41
+ quantity: 2,
42
+ price: 10,
43
+ is_tax_included: true,
44
+ tax_name: "Redusa",
45
+ tax_percentage: 9
46
+ )
47
+ ]
48
+ )
49
+
50
+ # 3. Emiterea facturii.
51
+ client.with_client do |c|
52
+ resp = c.invoices.create(invoice)
53
+ puts "Factura emisa: seria #{resp.series}, numarul #{resp.number}"
54
+ end
55
+ end
56
+
57
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: înregistrarea unei încasări pe o factură (sincron).
4
+ #
5
+ # Aici se emite o încasare de tip „Chitanta” care este legată de o factură
6
+ # existentă prin invoices_list (perechi serie + număr).
7
+ #
8
+ # Endpoint: POST /payment. Modelul Payment acoperă toate tipurile de
9
+ # încasări (chitanță, bon, card, ordin de plată etc.) prin câmpul type.
10
+ # Câmpurile care nu se aplică tipului ales se lasă nil.
11
+
12
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
13
+ require "smartbill/sdk"
14
+
15
+ def main
16
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
17
+ payment = Smartbill::Sdk::Models::Payment.new(
18
+ company_vat_code: "RO12345678",
19
+ client: Smartbill::Sdk::Models::Client.new(name: "Intelligent IT", vat_code: "RO12345678"),
20
+ value: 62.0,
21
+ type: Smartbill::Sdk::Models::PaymentType::CHITANTA,
22
+ is_cash: true,
23
+ invoices_list: [
24
+ Smartbill::Sdk::Models::InvoiceRef.new(series_name: "FCT", number: "0040")
25
+ ]
26
+ )
27
+
28
+ resp = client.payments.create(payment)
29
+ puts "Incasare inregistrata: seria #{resp.series}, numarul #{resp.number}"
30
+
31
+ # Ștergere chitanță (dacă ulterior se anulează).
32
+ # client.payments.delete_chitanta("RO12345678", resp.series, resp.number)
33
+ end
34
+ end
35
+
36
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: emiterea unui bon fiscal cu încasare mixtă (sincron).
4
+ #
5
+ # Bonul fiscal se emite tot prin POST /payment cu type='Bon'. Pe lângă
6
+ # produsele vândute, se pot specifica sumele încasate pe fiecare metodă
7
+ # de plată (cash, card, tichete de masă etc.) prin câmpurile received_*.
8
+ # Dacă return_fiscal_printer_text: true, API-ul returnează textul
9
+ # destinat imprimantei fiscale (în message).
10
+
11
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
12
+ require "smartbill/sdk"
13
+
14
+ def main
15
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
16
+ payment = Smartbill::Sdk::Models::Payment.new(
17
+ company_vat_code: "RO12345678",
18
+ value: 260.0,
19
+ type: "Bon",
20
+ is_cash: false,
21
+ use_stock: false,
22
+ return_fiscal_printer_text: true,
23
+ products: [
24
+ Smartbill::Sdk::Models::Product.new(
25
+ name: "Produs 1", measuring_unit_name: "buc", currency: "RON",
26
+ quantity: 1, price: 200, is_tax_included: true,
27
+ tax_name: "Normala", tax_percentage: 19
28
+ ),
29
+ Smartbill::Sdk::Models::Product.new(
30
+ name: "Produs 2", measuring_unit_name: "buc", currency: "RON",
31
+ quantity: 1, price: 60, is_tax_included: true,
32
+ tax_name: "Normala", tax_percentage: 19
33
+ )
34
+ ],
35
+ received_cash: 200.0,
36
+ received_card: 60.0
37
+ )
38
+
39
+ receipt = client.payments.create(payment)
40
+ puts "Bon fiscal emis: id=#{receipt.id}"
41
+ puts " Text imprimanta fiscala: #{receipt.message}"
42
+
43
+ # Starea plății pentru o factură deja existentă.
44
+ status = client.invoices.payment_status("RO12345678", "FCT", "0040")
45
+ puts "Stare plata factura: total=#{status.invoice_total_amount}, " \
46
+ "platit=#{status.paid_amount}, rest=#{status.unpaid_amount}"
47
+ end
48
+ end
49
+
50
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: stornare, anulare, restaurare factură și descărcare PDF (sincron).
4
+ #
5
+ # Acest exemplu ilustrează operațiile secundare pe facturi:
6
+ # * client.invoices.reverse(StornoRequest) -> POST /invoice/reverse
7
+ # * client.invoices.cancel(cif, series, number) -> PUT /invoice/cancel
8
+ # * client.invoices.restore(cif, series, number) -> PUT /invoice/restore
9
+ # * client.invoices.pdf(cif, series, number) -> GET /invoice/pdf (binary String)
10
+
11
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
12
+ require "smartbill/sdk"
13
+
14
+ def main
15
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
16
+ cif = "RO12345678"
17
+ series = "FCT"
18
+ number = "0040"
19
+
20
+ # 1. Stornare.
21
+ storno = Smartbill::Sdk::Models::StornoRequest.new(
22
+ company_vat_code: cif, series_name: series, number: number
23
+ )
24
+ st = client.invoices.reverse(storno)
25
+ puts "Factura storno: #{st.series} #{st.number}"
26
+ puts " Document URL: #{st.document_url}"
27
+
28
+ # 2. Anulare factură originală.
29
+ client.invoices.cancel(cif, series, number)
30
+ puts "Factura a fost anulata."
31
+
32
+ # 3. Restaurare factură (se poate face ulterior).
33
+ client.invoices.restore(cif, series, number)
34
+ puts "Factura a fost restaurata."
35
+
36
+ # 4. Descărcare PDF.
37
+ pdf_bytes = client.invoices.pdf(cif, series, number)
38
+ File.binwrite("factura.pdf", pdf_bytes)
39
+ puts "PDF salvat (#{pdf_bytes.bytesize} bytes)."
40
+ end
41
+ end
42
+
43
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: listarea seriilor de documente (sincron).
4
+ #
5
+ # Endpoint: GET /series. Returnează seriile (și următorul număr liber)
6
+ # pentru documentele din cont. Parametrul opțional type filtrează după
7
+ # tipul documentului:
8
+ # f - facturi, c - chitanțe, p - proforme, i - bonuri fiscale, n - avize
9
+ # Dacă type nu este dat, se returnează toate seriile.
10
+
11
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
12
+ require "smartbill/sdk"
13
+
14
+ def main
15
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
16
+ # Doar seriile de facturi.
17
+ facturi = client.series.series("RO12345678", type: "f")
18
+ puts "Serii facturi:"
19
+ facturi.list.each { |s| puts " - #{s.name}: urmatorul numar #{s.next_number}" }
20
+
21
+ # Toate seriile (fără filtru).
22
+ toate = client.series.series("RO12345678")
23
+ puts "Total serii (orice tip): #{toate.list.size}"
24
+ end
25
+ end
26
+
27
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: trimiterea pe e-mail a unui document (sincron).
4
+ #
5
+ # Endpoint: POST /document/send. Modelul EmailDocument identifică
6
+ # documentul prin serie + număr + tip (factura sau proforma) și permite
7
+ # specificarea destinatarilor (to, cc, bcc) și a conținutului.
8
+ #
9
+ # Important: câmpurile subject și body_text trebuie să fie codificate
10
+ # Base64 de către apelant, conform cerințelor API-ului SmartBill.
11
+
12
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
13
+ require "smartbill/sdk"
14
+ require "base64"
15
+
16
+ # Codifică un text în Base64 (necesar pentru subject/bodyText).
17
+ def b64(text)
18
+ Base64.strict_encode64(text)
19
+ end
20
+
21
+ def main
22
+ Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN").with_client do |client|
23
+ email = Smartbill::Sdk::Models::EmailDocument.new(
24
+ company_vat_code: "RO12345678",
25
+ series_name: "FCT",
26
+ number: "0040",
27
+ type: Smartbill::Sdk::Models::DocumentType::INVOICE,
28
+ to: "client@example.ro",
29
+ cc: "contabilitate@example.ro",
30
+ subject: b64("Factura FCT0040"),
31
+ body_text: b64("Va trimitem Factura FCT0040. Multumim!")
32
+ )
33
+
34
+ resp = client.email.send(email)
35
+ # status.code este "0" la succes.
36
+ puts "Status cod: #{resp.status.code}, mesaj: #{resp.status.message}"
37
+ end
38
+ end
39
+
40
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exemplu: listarea taxelor (TVA) și a stocurilor (sincron, concurent).
4
+ #
5
+ # Echivalentul Ruby al exemplelor asincrone din Python. Ruby nu are un
6
+ # client async dedicat în acest SDK, dar operațiile independente pot fi
7
+ # rulate concurent cu thread-uri (fiecare thread folosește propriul
8
+ # Smartbill::Sdk::Client, sau clientul shared — Net::HTTP deschide o
9
+ # conexiune nouă per request, deci este thread-safe în această privință).
10
+
11
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
12
+ require "smartbill/sdk"
13
+
14
+ CIF = "RO12345678"
15
+
16
+ def fetch_taxes(client)
17
+ client.taxes.taxes(CIF)
18
+ end
19
+
20
+ def fetch_stocks(client)
21
+ client.stocks.get(CIF, "2024-05-01")
22
+ end
23
+
24
+ def main
25
+ client = Smartbill::Sdk::Client.new(username: "you@example.com", token: "YOUR_TOKEN")
26
+
27
+ taxes_thread = Thread.new { fetch_taxes(client) }
28
+ stocks_thread = Thread.new { fetch_stocks(client) }
29
+
30
+ taxes = taxes_thread.value
31
+ stocks = stocks_thread.value
32
+
33
+ puts "Taxe:"
34
+ taxes.taxes.each { |t| puts " - #{t.name}: #{t.percentage}%" }
35
+
36
+ puts "Stocuri:"
37
+ stocks.list.each do |entry|
38
+ wh = entry.warehouse
39
+ puts " Gestiune: #{wh&.warehouse_name} (#{wh&.warehouse_type})"
40
+ entry.products.each do |p|
41
+ puts " - #{p.product_name} [#{p.product_code}]: #{p.quantity} #{p.measuring_unit}"
42
+ end
43
+ end
44
+ ensure
45
+ client&.close
46
+ end
47
+
48
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ # Raised when the SmartBill API returns an error envelope or a non-2xx
6
+ # response.
7
+ #
8
+ # @!attribute [r] error_text
9
+ # @return [String] the +errorText+ field from the API response.
10
+ # @!attribute [r] message_field
11
+ # @return [String, nil] the optional +message+ field from the API.
12
+ # @!attribute [r] status_code
13
+ # @return [Integer, nil] the HTTP status code, if available.
14
+ class APIError < Error
15
+ attr_reader :error_text, :message_field, :status_code
16
+
17
+ def initialize(error_text: "", message: nil, status_code: nil)
18
+ @error_text = error_text.to_s
19
+ @message_field = message
20
+ @status_code = status_code
21
+ detail = @error_text.empty? ? (message || "SmartBill API error") : @error_text
22
+ detail = "[#{status_code}] #{detail}" unless status_code.nil?
23
+ super(detail)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ # Raised on HTTP 401 (bad credentials / company CIF).
6
+ class AuthError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ # Synchronous client for the SmartBill Cloud REST API.
6
+ #
7
+ # Usage:
8
+ #
9
+ # client = Smartbill::Sdk::Client.new(username: "you@example.com", token: "...")
10
+ # resp = client.invoices.create(invoice)
11
+ # puts resp.series, resp.number
12
+ # client.close
13
+ #
14
+ # Or with a block (closes automatically):
15
+ #
16
+ # Smartbill::Sdk::Client.new(username: "...", token: "...").with_client do |client|
17
+ # client.invoices.create(invoice)
18
+ # end
19
+ class Client
20
+ attr_reader :username, :token, :base_url, :auth_header,
21
+ :invoices, :estimates, :payments, :email, :taxes, :series, :stocks
22
+
23
+ def initialize(username:, token:, base_url: Transport::DEFAULT_BASE_URL,
24
+ timeout: Transport::DEFAULT_TIMEOUT, enforce_rate_limit: false,
25
+ http: nil)
26
+ @username = username
27
+ @token = token
28
+ @base_url = base_url
29
+ @auth_header = Transport.build_auth(username, token)
30
+ @rate_limiter = enforce_rate_limit ? Transport::RateLimiter.new : nil
31
+ @http = http || NetHttpAdapter.new(timeout: timeout)
32
+ @invoices = Services::InvoicesService.new(self)
33
+ @estimates = Services::EstimatesService.new(self)
34
+ @payments = Services::PaymentsService.new(self)
35
+ @email = Services::EmailService.new(self)
36
+ @taxes = Services::ConfigurationService.new(self)
37
+ @series = @taxes # convenience alias: taxes + series share one service
38
+ @stocks = Services::StocksService.new(self)
39
+ end
40
+
41
+ # Send a {Transport::Request} and return the parsed payload.
42
+ # When +binary+ is true, the raw body String is returned.
43
+ def execute(request, binary: false)
44
+ @rate_limiter&.acquire
45
+ response = begin
46
+ @http.call(request)
47
+ rescue Error, AuthError, APIError, RateLimitError, ValidationError => e
48
+ raise e
49
+ rescue StandardError => e
50
+ raise TransportError, "Transport error: #{e.message}"
51
+ end
52
+ @rate_limiter&.notify_403 if response.status == 403 && @rate_limiter
53
+ Transport.handle_response(response, binary: binary)
54
+ end
55
+
56
+ # Close the underlying HTTP adapter. A no-op for the default
57
+ # +Net::HTTP+ adapter (which opens a fresh connection per request).
58
+ def close; end
59
+
60
+ # Yield self to a block and ensure +#close+ is called afterwards.
61
+ def with_client
62
+ yield self
63
+ ensure
64
+ close
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ module Contracts
6
+ # Base class for every SmartBill validation contract.
7
+ #
8
+ # Declares shared type predicates (e.g. {DATE_REGEX}) and provides
9
+ # {validate}, the helper services call to run a struct's attributes
10
+ # through the contract and raise {ValidationError} on failure.
11
+ class Base < Dry::Validation::Contract
12
+ # `YYYY-MM-DD`, matching the SmartBill API date format.
13
+ DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/
14
+
15
+ class << self
16
+ # Validate +struct+ (a {Models::Struct}) against this contract,
17
+ # raising {ValidationError} with the aggregated error messages
18
+ # when the contract fails. Returns the struct unchanged on success.
19
+ def validate!(struct)
20
+ result = new.call(struct.to_attributes)
21
+ return struct if result.success?
22
+
23
+ messages = result.errors.map do |err|
24
+ path = err.path.is_a?(Array) ? err.path.join(".") : err.path
25
+ "#{path} #{err.text}"
26
+ end
27
+ raise ValidationError, "Validation failed: #{messages.join("; ")}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ module Contracts
6
+ # Validation contract for {Models::EmailDocument} (+POST /document/send+).
7
+ #
8
+ # Validates +type+ (factura / proforma) and a basic shape check on the
9
+ # recipient addresses. +subject+ and +body_text+ must be Base64-encoded
10
+ # by the caller — this contract does not verify the encoding.
11
+ class EmailContract < Base
12
+ DOCUMENT_TYPES = %w[factura proforma].freeze
13
+ # Very permissive e-mail shape check — the SmartBill API does the
14
+ # authoritative validation.
15
+ EMAIL_REGEX = /\A[^@\s]+@[^@\s]+\z/
16
+
17
+ params do
18
+ required(:company_vat_code).filled(:string)
19
+ required(:series_name).filled(:string)
20
+ required(:number).filled(:string)
21
+ optional(:type).maybe(:string, included_in?: DOCUMENT_TYPES)
22
+ optional(:to).maybe(:string, format?: EMAIL_REGEX)
23
+ optional(:cc).maybe(:string, format?: EMAIL_REGEX)
24
+ optional(:bcc).maybe(:string, format?: EMAIL_REGEX)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ module Contracts
6
+ # Validation contract for {Models::Estimate} (proforma).
7
+ class EstimateContract < Base
8
+ params do
9
+ required(:company_vat_code).filled(:string)
10
+ optional(:issue_date).maybe(:string, format?: DATE_REGEX)
11
+ optional(:due_date).maybe(:string, format?: DATE_REGEX)
12
+ optional(:precision).maybe(:integer, gteq?: 0)
13
+ optional(:exchange_rate).maybe(:float, gt?: 0)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ module Contracts
6
+ # Validation contract for {Models::Invoice}.
7
+ #
8
+ # Adds semantic rules on top of the struct's shape/coercion: date
9
+ # fields must match +YYYY-MM-DD+, +precision+ must be a non-negative
10
+ # integer, and the embedded +payment+ (when present) must satisfy the
11
+ # {InvoicePaymentContract} rules.
12
+ class InvoiceContract < Base
13
+ params do
14
+ required(:company_vat_code).filled(:string)
15
+ optional(:issue_date).maybe(:string, format?: DATE_REGEX)
16
+ optional(:due_date).maybe(:string, format?: DATE_REGEX)
17
+ optional(:delivery_date).maybe(:string, format?: DATE_REGEX)
18
+ optional(:payment_date).maybe(:string, format?: DATE_REGEX)
19
+ optional(:precision).maybe(:integer, gteq?: 0)
20
+ optional(:exchange_rate).maybe(:float, gt?: 0)
21
+ optional(:payment).maybe(:hash) do
22
+ required(:value).filled(:float, gt?: 0)
23
+ required(:type).filled(:string)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end