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.
- checksums.yaml +7 -0
- data/AGENTS.md +211 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +265 -0
- data/Rakefile +22 -0
- data/Steepfile +6 -0
- data/docs/openapi.json +9741 -0
- data/examples/create_estimate_sync.rb +53 -0
- data/examples/create_invoice_eur_product_in_ron.rb +100 -0
- data/examples/create_invoice_sync.rb +57 -0
- data/examples/create_payment_sync.rb +36 -0
- data/examples/fiscal_receipt_sync.rb +50 -0
- data/examples/invoice_lifecycle_sync.rb +43 -0
- data/examples/list_series_sync.rb +27 -0
- data/examples/send_email_sync.rb +40 -0
- data/examples/taxes_and_stocks_sync.rb +48 -0
- data/lib/smartbill/sdk/api_error.rb +27 -0
- data/lib/smartbill/sdk/auth_error.rb +8 -0
- data/lib/smartbill/sdk/client.rb +68 -0
- data/lib/smartbill/sdk/contracts/base.rb +33 -0
- data/lib/smartbill/sdk/contracts/email_contract.rb +29 -0
- data/lib/smartbill/sdk/contracts/estimate_contract.rb +18 -0
- data/lib/smartbill/sdk/contracts/invoice_contract.rb +29 -0
- data/lib/smartbill/sdk/contracts/invoice_payment_contract.rb +18 -0
- data/lib/smartbill/sdk/contracts/invoice_ref_contract.rb +19 -0
- data/lib/smartbill/sdk/contracts/payment_contract.rb +37 -0
- data/lib/smartbill/sdk/contracts/storno_contract.rb +17 -0
- data/lib/smartbill/sdk/contracts.rb +19 -0
- data/lib/smartbill/sdk/error.rb +8 -0
- data/lib/smartbill/sdk/models/base_response.rb +16 -0
- data/lib/smartbill/sdk/models/client.rb +26 -0
- data/lib/smartbill/sdk/models/discount_type.rb +13 -0
- data/lib/smartbill/sdk/models/document_type.rb +13 -0
- data/lib/smartbill/sdk/models/email_document.rb +23 -0
- data/lib/smartbill/sdk/models/email_response.rb +12 -0
- data/lib/smartbill/sdk/models/email_status.rb +13 -0
- data/lib/smartbill/sdk/models/estimate.rb +32 -0
- data/lib/smartbill/sdk/models/fiscal_receipt_response.rb +16 -0
- data/lib/smartbill/sdk/models/invoice.rb +44 -0
- data/lib/smartbill/sdk/models/invoice_create_response.rb +10 -0
- data/lib/smartbill/sdk/models/invoice_payment.rb +15 -0
- data/lib/smartbill/sdk/models/invoice_ref.rb +24 -0
- data/lib/smartbill/sdk/models/payment.rb +49 -0
- data/lib/smartbill/sdk/models/payment_status_response.rb +19 -0
- data/lib/smartbill/sdk/models/payment_type.rb +22 -0
- data/lib/smartbill/sdk/models/product.rb +38 -0
- data/lib/smartbill/sdk/models/proforma_invoices_response.rb +13 -0
- data/lib/smartbill/sdk/models/series.rb +14 -0
- data/lib/smartbill/sdk/models/series_list_response.rb +14 -0
- data/lib/smartbill/sdk/models/stock_list.rb +13 -0
- data/lib/smartbill/sdk/models/stock_product.rb +15 -0
- data/lib/smartbill/sdk/models/stock_warehouse.rb +13 -0
- data/lib/smartbill/sdk/models/stocks_response.rb +14 -0
- data/lib/smartbill/sdk/models/storno_request.rb +17 -0
- data/lib/smartbill/sdk/models/storno_response.rb +14 -0
- data/lib/smartbill/sdk/models/struct.rb +102 -0
- data/lib/smartbill/sdk/models/tax.rb +14 -0
- data/lib/smartbill/sdk/models/taxes_response.rb +14 -0
- data/lib/smartbill/sdk/models.rb +17 -0
- data/lib/smartbill/sdk/net_http_adapter.rb +62 -0
- data/lib/smartbill/sdk/rate_limit_error.rb +9 -0
- data/lib/smartbill/sdk/response.rb +12 -0
- data/lib/smartbill/sdk/services/base_service.rb +39 -0
- data/lib/smartbill/sdk/services/configuration_service.rb +29 -0
- data/lib/smartbill/sdk/services/email_service.rb +18 -0
- data/lib/smartbill/sdk/services/estimates_service.rb +60 -0
- data/lib/smartbill/sdk/services/invoices_service.rb +69 -0
- data/lib/smartbill/sdk/services/payments_service.rb +50 -0
- data/lib/smartbill/sdk/services/stocks_service.rb +21 -0
- data/lib/smartbill/sdk/services.rb +30 -0
- data/lib/smartbill/sdk/transport/rate_limiter.rb +49 -0
- data/lib/smartbill/sdk/transport/request.rb +17 -0
- data/lib/smartbill/sdk/transport.rb +162 -0
- data/lib/smartbill/sdk/transport_error.rb +8 -0
- data/lib/smartbill/sdk/types.rb +22 -0
- data/lib/smartbill/sdk/validation_error.rb +8 -0
- data/lib/smartbill/sdk/version.rb +7 -0
- data/lib/smartbill/sdk.rb +31 -0
- data/sig/smartbill/sdk.rbs +661 -0
- data/skills/README.md +35 -0
- data/skills/smartbill-email/SKILL.md +120 -0
- data/skills/smartbill-invoices/SKILL.md +178 -0
- data/skills/smartbill-payments/SKILL.md +194 -0
- 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,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
|