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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2972838a84f90174e2d1caca6e3dab19b49a8773d0f1610e8b92a2d3cc11f17c
|
|
4
|
+
data.tar.gz: 8827e1879f9d53a2325593f9c1251db93d1310cba5975b62e60c5dbd83b80c45
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ca3c185e7665607ec61d3ad976be61525e38d3dcb85bc5d05c762d3802296af0c0d251fbb65c191c43b65a9974708282ec16eaac198f25d7a2f64a4741977fa7
|
|
7
|
+
data.tar.gz: 25490736acc0f2d4ccd3ba1e5a48ff1961ba5acaece48d530afa3a9f45cc5a98271e708f2ca9c1654e4416a1ac1b66cb3187e28c6382bae6f5714d1e99a5d463
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# PROJECT KNOWLEDGE BASE
|
|
2
|
+
|
|
3
|
+
**Generated:** 2026-06-21
|
|
4
|
+
|
|
5
|
+
## OVERVIEW
|
|
6
|
+
Project: **smartbill-sdk** (gem name `smartbill-sdk`, namespace `Smartbill::Sdk`)
|
|
7
|
+
Stack: Ruby ≥ 3.2, stdlib `Net::HTTP` + `base64` + `json`, models via
|
|
8
|
+
`dry-struct` + `dry-types` + `dry-inflector`, request validation via
|
|
9
|
+
`dry-validation`, autoloading via `zeitwerk`; build via Bundler/gemspec;
|
|
10
|
+
tests with `minitest` + `WebMock`; lint with `RuboCop`.
|
|
11
|
+
|
|
12
|
+
A Ruby SDK for the [SmartBill Cloud REST API](https://www.facturionline.ro/api-program-facturare/)
|
|
13
|
+
offering a **synchronous** `Smartbill::Sdk::Client` with typed request/response
|
|
14
|
+
models covering every endpoint in the official `openapi.json` spec. It is a
|
|
15
|
+
faithful Ruby port of the Python `smartbill-rest-sdk`
|
|
16
|
+
(see `/home/dnutiu/PycharmProjects/smartbill-sdk`).
|
|
17
|
+
|
|
18
|
+
## STRUCTURE
|
|
19
|
+
|
|
20
|
+
Constants under `Smartbill::Sdk` are **autoloaded by Zeitwerk** from
|
|
21
|
+
`lib/smartbill/sdk/`, following the **one file per constant** convention
|
|
22
|
+
(the entry file `lib/smartbill/sdk.rb` sets up a `Zeitwerk::Loader` with
|
|
23
|
+
`push_dir(..., namespace: Smartbill::Sdk)`). No `require_relative` chains —
|
|
24
|
+
constants resolve on first reference. Inflection overrides map `version.rb`
|
|
25
|
+
→ `VERSION` and `api_error.rb` → `APIError`.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
lib/smartbill/sdk.rb # entry point: Zeitwerk loader setup + DEFAULT_BASE_URL alias
|
|
29
|
+
lib/smartbill/sdk/version.rb # Smartbill::Sdk::VERSION
|
|
30
|
+
lib/smartbill/sdk/error.rb # Smartbill::Sdk::Error (base)
|
|
31
|
+
lib/smartbill/sdk/auth_error.rb # AuthError (HTTP 401)
|
|
32
|
+
lib/smartbill/sdk/rate_limit_error.rb # RateLimitError (HTTP 403)
|
|
33
|
+
lib/smartbill/sdk/transport_error.rb # TransportError
|
|
34
|
+
lib/smartbill/sdk/validation_error.rb # ValidationError (missing required fields)
|
|
35
|
+
lib/smartbill/sdk/api_error.rb # APIError (.error_text/.message_field/.status_code)
|
|
36
|
+
lib/smartbill/sdk/response.rb # Response struct (status/body/content_type)
|
|
37
|
+
lib/smartbill/sdk/net_http_adapter.rb # NetHttpAdapter (default HTTP adapter, Net::HTTP)
|
|
38
|
+
lib/smartbill/sdk/transport.rb # Transport module: build_auth, build_request, parse_envelope, handle_response + constants
|
|
39
|
+
lib/smartbill/sdk/transport/request.rb # Transport::Request struct
|
|
40
|
+
lib/smartbill/sdk/transport/rate_limiter.rb # Transport::RateLimiter
|
|
41
|
+
lib/smartbill/sdk/client.rb # Client (sync) + services wiring
|
|
42
|
+
lib/smartbill/sdk/types.rb # Types (Dry::Types) + StrOrInt sum type
|
|
43
|
+
lib/smartbill/sdk/models.rb # Models namespace + INFLECTOR
|
|
44
|
+
lib/smartbill/sdk/models/struct.rb # Struct < Dry::Struct: snake_case<->camelCase, to_h, to_attributes, ValidationError translation
|
|
45
|
+
lib/smartbill/sdk/models/document_type.rb # DocumentType (constants)
|
|
46
|
+
lib/smartbill/sdk/models/payment_type.rb # PaymentType (constants)
|
|
47
|
+
lib/smartbill/sdk/models/discount_type.rb # DiscountType (constants)
|
|
48
|
+
lib/smartbill/sdk/models/client.rb # Client
|
|
49
|
+
lib/smartbill/sdk/models/product.rb # Product
|
|
50
|
+
lib/smartbill/sdk/models/invoice_ref.rb # InvoiceRef (series/seriesName alias)
|
|
51
|
+
lib/smartbill/sdk/models/invoice_payment.rb # InvoicePayment
|
|
52
|
+
lib/smartbill/sdk/models/invoice.rb # Invoice
|
|
53
|
+
lib/smartbill/sdk/models/storno_request.rb # StornoRequest
|
|
54
|
+
lib/smartbill/sdk/models/estimate.rb # Estimate
|
|
55
|
+
lib/smartbill/sdk/models/payment.rb # Payment
|
|
56
|
+
lib/smartbill/sdk/models/email_document.rb # EmailDocument
|
|
57
|
+
lib/smartbill/sdk/models/tax.rb # Tax
|
|
58
|
+
lib/smartbill/sdk/models/series.rb # Series
|
|
59
|
+
lib/smartbill/sdk/models/taxes_response.rb # TaxesResponse
|
|
60
|
+
lib/smartbill/sdk/models/series_list_response.rb # SeriesListResponse
|
|
61
|
+
lib/smartbill/sdk/models/stock_product.rb # StockProduct
|
|
62
|
+
lib/smartbill/sdk/models/stock_warehouse.rb # StockWarehouse
|
|
63
|
+
lib/smartbill/sdk/models/stock_list.rb # StockList
|
|
64
|
+
lib/smartbill/sdk/models/stocks_response.rb # StocksResponse
|
|
65
|
+
lib/smartbill/sdk/models/base_response.rb # BaseResponse
|
|
66
|
+
lib/smartbill/sdk/models/invoice_create_response.rb
|
|
67
|
+
lib/smartbill/sdk/models/storno_response.rb
|
|
68
|
+
lib/smartbill/sdk/models/payment_status_response.rb
|
|
69
|
+
lib/smartbill/sdk/models/proforma_invoices_response.rb
|
|
70
|
+
lib/smartbill/sdk/models/email_status.rb
|
|
71
|
+
lib/smartbill/sdk/models/email_response.rb
|
|
72
|
+
lib/smartbill/sdk/models/fiscal_receipt_response.rb
|
|
73
|
+
lib/smartbill/sdk/services.rb # Services namespace + dump_model/parse helpers
|
|
74
|
+
lib/smartbill/sdk/services/base_service.rb # BaseService
|
|
75
|
+
lib/smartbill/sdk/services/invoices_service.rb # InvoicesService
|
|
76
|
+
lib/smartbill/sdk/services/estimates_service.rb # EstimatesService
|
|
77
|
+
lib/smartbill/sdk/services/payments_service.rb # PaymentsService
|
|
78
|
+
lib/smartbill/sdk/services/email_service.rb # EmailService
|
|
79
|
+
lib/smartbill/sdk/services/configuration_service.rb # ConfigurationService (taxes + series)
|
|
80
|
+
lib/smartbill/sdk/services/stocks_service.rb # StocksService
|
|
81
|
+
lib/smartbill/sdk/contracts.rb # Contracts namespace (dry-validation)
|
|
82
|
+
lib/smartbill/sdk/contracts/base.rb # Contracts::Base < Dry::Validation::Contract + DATE_REGEX + validate!
|
|
83
|
+
lib/smartbill/sdk/contracts/invoice_contract.rb
|
|
84
|
+
lib/smartbill/sdk/contracts/estimate_contract.rb
|
|
85
|
+
lib/smartbill/sdk/contracts/payment_contract.rb
|
|
86
|
+
lib/smartbill/sdk/contracts/email_contract.rb
|
|
87
|
+
lib/smartbill/sdk/contracts/storno_contract.rb
|
|
88
|
+
lib/smartbill/sdk/contracts/invoice_payment_contract.rb
|
|
89
|
+
lib/smartbill/sdk/contracts/invoice_ref_contract.rb
|
|
90
|
+
test/ # minitest + WebMock suite (60 tests)
|
|
91
|
+
examples/ # standalone runnable Ruby scripts
|
|
92
|
+
skills/ # pi agent skills (SKILL.md per API area)
|
|
93
|
+
sig/smartbill/sdk.rbs # RBS type signatures for the full public API
|
|
94
|
+
Steepfile # opt-in Steep typecheck configuration
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## COMMANDS
|
|
98
|
+
| Action | Command |
|
|
99
|
+
|---------------------|----------------------------------|
|
|
100
|
+
| Install (dev) | `bundle install` |
|
|
101
|
+
| Run tests | `bundle exec rake test` |
|
|
102
|
+
| Run a single test | `bundle exec ruby -Ilib:test test/smartbill/test_invoices.rb` |
|
|
103
|
+
| Lint | `bundle exec rubocop` |
|
|
104
|
+
| Auto-correct lint | `bundle exec rubocop -A` |
|
|
105
|
+
| Build gem | `bundle exec rake build` |
|
|
106
|
+
| Console | `bin/console` |
|
|
107
|
+
| Run an example | `bundle exec ruby examples/create_invoice_sync.rb` |
|
|
108
|
+
| Validate RBS sigs | `bundle exec rake rbs:validate` |
|
|
109
|
+
| Typecheck (opt-in) | `bundle exec steep check` (needs dry-rb RBS; see note below) |
|
|
110
|
+
|
|
111
|
+
## CODING STANDARDS
|
|
112
|
+
* **Language**: Ruby 3.2+; `# frozen_string_literal: true` everywhere.
|
|
113
|
+
* **Autoloading**: Zeitwerk, one file per constant. To add a new class
|
|
114
|
+
`Smartbill::Sdk::Foo::Bar`, create `lib/smartbill/sdk/foo/bar.rb`
|
|
115
|
+
defining `class Bar` (and a `lib/smartbill/sdk/foo.rb` namespace file
|
|
116
|
+
`module Foo; end` if the directory is new). Do **not** add
|
|
117
|
+
`require_relative` for SDK-internal constants — let Zeitwerk resolve
|
|
118
|
+
them. Only `require` stdlib/gems (`base64`, `json`, `net/http`, `uri`,
|
|
119
|
+
`zeitwerk`) at the top of files that use them. If a file basename does
|
|
120
|
+
not camelcase to the desired constant (e.g. `api_error.rb` → `APIError`,
|
|
121
|
+
`version.rb` → `VERSION`), add an entry to `loader.inflector.inflect`
|
|
122
|
+
in `lib/smartbill/sdk.rb`.
|
|
123
|
+
* **Style**: 2-space indent, double-quote strings (enforced by RuboCop).
|
|
124
|
+
* **Models**: subclass `Smartbill::Sdk::Models::Struct` (a `Dry::Struct`)
|
|
125
|
+
and declare attributes with the dry-struct `attribute` DSL using types
|
|
126
|
+
from `Smartbill::Sdk::Types` (e.g. `attribute :company_vat_code,
|
|
127
|
+
Types::Strict::String`; `attribute :price, Types::Coercible::Float.optional.default(nil)`;
|
|
128
|
+
`attribute :products, Types::Array.of(Product).default([].freeze)`).
|
|
129
|
+
The base `Struct` handles: snake_case/camelCase input key normalization
|
|
130
|
+
(`transform_keys` + `Dry::Inflector`), camelCase output via `#to_h`
|
|
131
|
+
(nils omitted by default), recursive nested serialization, and
|
|
132
|
+
`Dry::Struct::Error` → `ValidationError` translation. Mixed string/int
|
|
133
|
+
fields use `Types::StrOrInt`. Unknown input keys are **ignored** (not
|
|
134
|
+
preserved) — permissive parsing without re-emission. Use `#to_attributes`
|
|
135
|
+
for the snake_case hash (with nils, nested structs → hashes) fed to
|
|
136
|
+
contracts.
|
|
137
|
+
* **Enums**: plain modules of string/int constants (`PaymentType::CHITANTA`
|
|
138
|
+
== `"Chitanta"`). Pass the constant; it is stored/serialized as-is.
|
|
139
|
+
* **Validation**: each request model has a `Smartbill::Sdk::Contracts::*Contract`
|
|
140
|
+
(`< Contracts::Base < Dry::Validation::Contract`). The contract's `params`
|
|
141
|
+
block declares only the fields with semantic rules (dates via
|
|
142
|
+
`DATE_REGEX`, enum membership via `included_in?`, ranges, nested hashes);
|
|
143
|
+
unknown keys are ignored by dry-validation. Services call
|
|
144
|
+
`validate(struct, Contracts::XContract)` before sending, which runs
|
|
145
|
+
`Contract.validate!(struct)` → `contract.new.call(struct.to_attributes)`
|
|
146
|
+
and raises `ValidationError` with aggregated `path text` messages on
|
|
147
|
+
failure. To add a rule, edit the contract's `params` block — do **not**
|
|
148
|
+
re-declare every field, only the ones you validate.
|
|
149
|
+
* **Services**: each service builds a `Transport::Request` via
|
|
150
|
+
`Transport.build_request(...)` and parses the response with
|
|
151
|
+
`Services.parse(payload, ModelClass)`. Services get the client (executor)
|
|
152
|
+
injected; they call `@client.execute(request, binary: ...)`. Request-taking
|
|
153
|
+
methods (`invoices.create`, `invoices.reverse`, `estimates.create`,
|
|
154
|
+
`payments.create`, `email.send`) call `validate(model, Contracts::XContract)`
|
|
155
|
+
**before** building the request so invalid input never hits the network.
|
|
156
|
+
* **Errors**: raise `Smartbill::Sdk::Error` subclasses (one per file under
|
|
157
|
+
`lib/smartbill/sdk/`); never bare `RuntimeError`. `Transport.handle_response`
|
|
158
|
+
is the single place that maps HTTP status / envelopes to exceptions.
|
|
159
|
+
* **HTTP adapter**: the client talks to a swappable adapter through one
|
|
160
|
+
method `#call(req) -> Response`. The default is `NetHttpAdapter`
|
|
161
|
+
(stdlib `Net::HTTP`). Inject a custom `http:` object in tests/for other
|
|
162
|
+
backends.
|
|
163
|
+
|
|
164
|
+
## WHERE TO LOOK
|
|
165
|
+
* **Source**: `lib/smartbill/sdk/`
|
|
166
|
+
* **Tests**: `test/` (HTTP mocked via WebMock; `test/test_helper.rb` has
|
|
167
|
+
shared helpers: `make_client`, `envelope`, `last_request`, `query_of`,
|
|
168
|
+
`assert_auth`, `assert_json_headers`)
|
|
169
|
+
* **Docs**: `README.md`, `examples/`
|
|
170
|
+
* **Public API surface**: `lib/smartbill/sdk.rb` and `Smartbill::Sdk::Client`
|
|
171
|
+
|
|
172
|
+
## NOTES
|
|
173
|
+
* **Auth**: HTTP Basic Auth with `username:token` where `username` is the
|
|
174
|
+
login e-mail and `token` comes from SmartBill Cloud → *Contul Meu →
|
|
175
|
+
Integrari → API*.
|
|
176
|
+
* **JSON only**: the SDK sends JSON; XML is not supported.
|
|
177
|
+
* **Rate limit**: 30 calls / 10 seconds — exceeding it triggers a
|
|
178
|
+
server-side 403 that blocks access for 10 minutes. Opt into a
|
|
179
|
+
client-side preemptive limiter with `Client.new(..., enforce_rate_limit: true)`.
|
|
180
|
+
* **Dates**: all date fields are `YYYY-MM-DD` strings, matching the API.
|
|
181
|
+
* **Sync only**: there is no async client (Ruby's concurrency model
|
|
182
|
+
differs from Python's asyncio). For concurrency, run independent
|
|
183
|
+
requests on separate threads — `Net::HTTP` opens a fresh connection
|
|
184
|
+
per request so this is safe (see `examples/taxes_and_stocks_sync.rb`).
|
|
185
|
+
* **`taxes` / `series` alias**: on a `Client`, `client.taxes` and
|
|
186
|
+
`client.series` are the *same* `ConfigurationService` instance.
|
|
187
|
+
* **Tests are mocked**: no network calls; WebMock intercepts `Net::HTTP`.
|
|
188
|
+
The full suite (60 tests) passes offline.
|
|
189
|
+
* **Runtime dependencies**: `base64` (no longer a default gem in Ruby
|
|
190
|
+
3.4+), `zeitwerk` (autoloader), `dry-struct` + `dry-types` +
|
|
191
|
+
`dry-inflector` (models) and `dry-validation` (request contracts). All
|
|
192
|
+
are declared in the gemspec.
|
|
193
|
+
* **Port origin**: this codebase was ported from the Python
|
|
194
|
+
`smartbill-rest-sdk`; behavior, models and endpoint coverage match.
|
|
195
|
+
* **Agent skills**: `skills/` ships three pi `SKILL.md` files
|
|
196
|
+
(`smartbill-invoices`, `smartbill-payments`, `smartbill-email`) that
|
|
197
|
+
teach coding agents how to use the SDK, with Ruby code snippets.
|
|
198
|
+
* **Type signatures**: `sig/smartbill/sdk.rbs` contains RBS signatures for
|
|
199
|
+
the entire public API — `Client`, all `Services::*`, all `Models::*`
|
|
200
|
+
(with read-only `attr_reader` per attribute and nil-able `?` types for
|
|
201
|
+
optional fields), all `Contracts::*`, the `Transport` module + `Request`/
|
|
202
|
+
`Response`/`RateLimiter`, `NetHttpAdapter`, and the `Error` hierarchy.
|
|
203
|
+
`bundle exec rake rbs:validate` checks syntax + consistency (passes
|
|
204
|
+
clean). `steep check` is configured via the `Steepfile` but cannot fully
|
|
205
|
+
typecheck the implementation because `dry-struct` / `dry-validation` do
|
|
206
|
+
not ship RBS signatures (their DSL — `attribute`, `optional`, `required`,
|
|
207
|
+
`filled`, `params`, ... — reports as `NoMethod`); this is an inherent
|
|
208
|
+
limitation, not a signature defect. When adding a public class/method,
|
|
209
|
+
add its signature to `sig/smartbill/sdk.rbs` in the matching namespace
|
|
210
|
+
section and keep `rake rbs:validate` green.
|
|
211
|
+
* No other context files (`.cursorrules`, `CLAUDE.md`, etc.) exist.
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-06-21
|
|
4
|
+
|
|
5
|
+
Ruby port of the Python `smartbill-rest-sdk` (v1.0.x).
|
|
6
|
+
|
|
7
|
+
- Synchronous `Smartbill::Sdk::Client` backed by stdlib `Net::HTTP`.
|
|
8
|
+
- Typed request/response models (snake_case Ruby attrs ↔ camelCase JSON)
|
|
9
|
+
for invoices, proformas (estimates), payments, fiscal receipts, e-mail,
|
|
10
|
+
taxes, series and stocks.
|
|
11
|
+
- Envelope unwrapping and a full error hierarchy (`Error`, `AuthError`,
|
|
12
|
+
`RateLimitError`, `APIError`, `TransportError`, `ValidationError`).
|
|
13
|
+
- Optional client-side `RateLimiter` (30 calls / 10s).
|
|
14
|
+
- WebMock-based test suite (60 tests) and runnable examples.
|
|
15
|
+
|
|
16
|
+
## [0.1.0] - 2026-06-21
|
|
17
|
+
|
|
18
|
+
- Initial scaffold (bundle gem).
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denis Nutiu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# smartbill-sdk
|
|
2
|
+
|
|
3
|
+
A Ruby SDK for the [SmartBill Cloud REST API](https://www.facturionline.ro/api-program-facturare/),
|
|
4
|
+
offering a synchronous client with typed request/response models covering
|
|
5
|
+
every endpoint in the official `openapi.json` spec.
|
|
6
|
+
|
|
7
|
+
This is a Ruby port of the Python
|
|
8
|
+
[`smartbill-rest-sdk`](https://github.com/dnutiu/Smart-Bill-SDK).
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Synchronous `Smartbill::Sdk::Client`.
|
|
13
|
+
- Typed request/response models built on **dry-struct** (type coercion,
|
|
14
|
+
required-attribute presence, snake_case ⇄ camelCase aliasing) with
|
|
15
|
+
**dry-validation** contracts enforcing semantic rules (date formats,
|
|
16
|
+
payment-type enum, positive amounts, recipient e-mail shape) before
|
|
17
|
+
every request is sent.
|
|
18
|
+
- snake_case Ruby attributes aliased to camelCase JSON automatically
|
|
19
|
+
(`company_vat_code` ↔ `companyVatCode`).
|
|
20
|
+
- Permissive parsing — unknown API fields are ignored, so new fields
|
|
21
|
+
don't break parsing.
|
|
22
|
+
- Helper exception hierarchy with the API `errorText` surfaced.
|
|
23
|
+
- Optional client-side rate limiter (SmartBill allows 30 calls / 10s,
|
|
24
|
+
then blocks for 10 minutes).
|
|
25
|
+
- Runtime dependencies: `dry-struct`, `dry-validation`, `dry-types`,
|
|
26
|
+
`dry-inflector`, `zeitwerk` (autoloading), and the stdlib `base64`
|
|
27
|
+
gem (uses `Net::HTTP` under the hood).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Add to your application's Gemfile:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem "smartbill-sdk"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or install manually:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gem install smartbill-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
From source (development):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bundle install
|
|
47
|
+
bundle exec rake test # run the test suite
|
|
48
|
+
bundle exec rake rubocop # lint
|
|
49
|
+
bundle exec rake rbs:validate # validate the RBS type signatures
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The gem ships [RBS](https://github.com/ruby/rbs) type signatures in
|
|
53
|
+
`sig/smartbill/sdk.rbs` covering the full public API (client, services,
|
|
54
|
+
models, contracts, transport, exceptions). Opt-in typechecking with
|
|
55
|
+
[Steep](https://github.com/soutaro/steep) is configured via the `Steepfile`,
|
|
56
|
+
though a fully clean `steep check` currently requires RBS signatures for
|
|
57
|
+
`dry-struct` / `dry-validation`, which are not bundled with those gems.
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
SmartBill uses HTTP Basic Auth with `username:token`:
|
|
62
|
+
|
|
63
|
+
- `username` — the e-mail you log in with in SmartBill Cloud.
|
|
64
|
+
- `token` — found in SmartBill Cloud > **Contul Meu** > **Integrari** > **API**.
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require "smartbill/sdk"
|
|
68
|
+
|
|
69
|
+
client = Smartbill::Sdk::Client.new(username: "you@example.com", token: "abc123...")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
require "smartbill/sdk"
|
|
76
|
+
|
|
77
|
+
include Smartbill::Sdk
|
|
78
|
+
|
|
79
|
+
client = Client.new(username: "you@example.com", token: "...")
|
|
80
|
+
|
|
81
|
+
invoice = Models::Invoice.new(
|
|
82
|
+
company_vat_code: "RO12345678",
|
|
83
|
+
client: Models::Client.new(name: "Intelligent IT", vat_code: "RO12345678",
|
|
84
|
+
city: "Sibiu", country: "Romania"),
|
|
85
|
+
series_name: "FCT",
|
|
86
|
+
is_draft: false,
|
|
87
|
+
products: [
|
|
88
|
+
Models::Product.new(name: "Produs 1", measuring_unit_name: "buc", currency: "RON",
|
|
89
|
+
quantity: 2, price: 10, is_tax_included: true,
|
|
90
|
+
tax_name: "Redusa", tax_percentage: 9)
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
resp = client.invoices.create(invoice)
|
|
95
|
+
puts "Factura emisa: seria #{resp.series}, numarul #{resp.number}"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The client can also be used with a block that closes it automatically:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
Client.new(username: "...", token: "...").with_client do |c|
|
|
102
|
+
c.invoices.create(invoice)
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Services
|
|
107
|
+
|
|
108
|
+
| Attribute | Service | Covers |
|
|
109
|
+
|--------------------|--------------------------|---------------------------------------------------------------|
|
|
110
|
+
| `client.invoices` | `InvoicesService` | create, delete, reverse (storno), cancel, restore, payment status, PDF |
|
|
111
|
+
| `client.estimates` | `EstimatesService` | create, delete, cancel, restore, PDF, invoices-status |
|
|
112
|
+
| `client.payments` | `PaymentsService` | create (general / chitanta / bon fiscal), delete, fiscal-receipt text |
|
|
113
|
+
| `client.email` | `EmailService` | `POST /document/send` |
|
|
114
|
+
| `client.taxes` | `ConfigurationService` | `GET /tax` (taxes), `GET /series` (series) |
|
|
115
|
+
| `client.series` | `ConfigurationService` | alias of `client.taxes` — same instance |
|
|
116
|
+
| `client.stocks` | `StocksService` | `GET /stocks` |
|
|
117
|
+
|
|
118
|
+
### Lifecycle: storno / cancel / restore / PDF
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
storno = Models::StornoRequest.new(company_vat_code: cif, series_name: "FCT", number: "0040")
|
|
122
|
+
st = client.invoices.reverse(storno)
|
|
123
|
+
puts st.document_url
|
|
124
|
+
|
|
125
|
+
client.invoices.cancel(cif, "FCT", "0040") # PUT /invoice/cancel
|
|
126
|
+
client.invoices.restore(cif, "FCT", "0040") # PUT /invoice/restore
|
|
127
|
+
|
|
128
|
+
pdf_bytes = client.invoices.pdf(cif, "FCT", "0040") # raw binary String
|
|
129
|
+
File.binwrite("factura.pdf", pdf_bytes)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Taxes, series and stocks
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
taxes = client.taxes.taxes("RO12345678")
|
|
136
|
+
taxes.taxes.each { |t| puts "#{t.name}: #{t.percentage}%" }
|
|
137
|
+
|
|
138
|
+
series = client.series.series("RO12345678", type: "f") # type: f/c/p/i/n
|
|
139
|
+
series.list.each { |s| puts "#{s.name}: #{s.next_number}" }
|
|
140
|
+
|
|
141
|
+
stocks = client.stocks.get("RO12345678", "2024-05-01", warehouse_name: "Depozit")
|
|
142
|
+
stocks.list.each { |entry| entry.products.each { |p| puts p.product_name } }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### E-mail
|
|
146
|
+
|
|
147
|
+
`subject` and `body_text` must be Base64-encoded by the caller, as the
|
|
148
|
+
SmartBill API requires.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
email = Models::EmailDocument.new(
|
|
152
|
+
company_vat_code: "RO12345678", series_name: "FCT", number: "0040",
|
|
153
|
+
type: Models::DocumentType::INVOICE, to: "client@example.ro",
|
|
154
|
+
subject: Base64.strict_encode64("Factura FCT0040"),
|
|
155
|
+
body_text: Base64.strict_encode64("Va trimitem Factura FCT0040.")
|
|
156
|
+
)
|
|
157
|
+
resp = client.email.send(email)
|
|
158
|
+
puts resp.status.code, resp.status.message
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Errors
|
|
162
|
+
|
|
163
|
+
All errors descend from `Smartbill::Sdk::Error`:
|
|
164
|
+
|
|
165
|
+
- `AuthError` — HTTP 401 (bad username/token/company CIF).
|
|
166
|
+
- `RateLimitError` — HTTP 403 (rate-limited, blocked 10 min).
|
|
167
|
+
- `APIError` — has `.error_text`, `.message_field`, `.status_code`
|
|
168
|
+
(the API's `errorText` is surfaced in `.error_text`).
|
|
169
|
+
- `TransportError` — network-level failure.
|
|
170
|
+
- `ValidationError` — a model is missing required fields or fails its
|
|
171
|
+
validation contract (bad date format, unknown payment type, non-positive
|
|
172
|
+
amount, etc.). Raised before any HTTP call is made.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
rescue Smartbill::Sdk::AuthError => e
|
|
176
|
+
# ...
|
|
177
|
+
rescue Smartbill::Sdk::APIError => e
|
|
178
|
+
puts e.error_text, e.status_code
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Validation
|
|
183
|
+
|
|
184
|
+
Request models are checked against a dry-validation contract before being
|
|
185
|
+
sent, so malformed requests raise `ValidationError` locally instead of
|
|
186
|
+
round-tripping to the SmartBill API. The contracts enforce:
|
|
187
|
+
|
|
188
|
+
- date fields match `YYYY-MM-DD`;
|
|
189
|
+
- `Payment#type` is one of the SmartBill payment types;
|
|
190
|
+
- `EmailDocument#type` is `factura` / `proforma` and recipients look like
|
|
191
|
+
e-mail addresses;
|
|
192
|
+
- numeric amounts are positive; `precision` is a non-negative integer;
|
|
193
|
+
- nested payment-at-issuance blocks (`Invoice#payment`) are validated too.
|
|
194
|
+
|
|
195
|
+
You can also run a contract explicitly:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
Smartbill::Sdk::Contracts::InvoiceContract.validate!(invoice) # raises ValidationError
|
|
199
|
+
result = Smartbill::Sdk::Contracts::InvoiceContract.new.call(invoice.to_attributes)
|
|
200
|
+
result.success? # => true / false
|
|
201
|
+
result.errors.to_h # => { issue_date: ["is in invalid format"] }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Rate limiting
|
|
205
|
+
|
|
206
|
+
SmartBill allows 30 calls / 10 seconds; exceeding it triggers a server-side
|
|
207
|
+
403 that blocks access for 10 minutes. Opt into a client-side preemptive
|
|
208
|
+
limiter with `enforce_rate_limit:`:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
client = Smartbill::Sdk::Client.new(username: "...", token: "...", enforce_rate_limit: true)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Notes
|
|
215
|
+
|
|
216
|
+
- The SDK talks JSON only (`format="json"`); XML is not supported.
|
|
217
|
+
- All date fields use `YYYY-MM-DD` strings, matching the API.
|
|
218
|
+
- Only a synchronous client is provided. For concurrency, run
|
|
219
|
+
independent requests on separate threads (each `Net::HTTP` request
|
|
220
|
+
opens its own connection — see `examples/taxes_and_stocks_sync.rb`).
|
|
221
|
+
|
|
222
|
+
## Examples
|
|
223
|
+
|
|
224
|
+
Runnable scripts live in [`examples/`](examples/):
|
|
225
|
+
|
|
226
|
+
| Script | Demonstrates |
|
|
227
|
+
|---------------------------------|------------------------------------------------|
|
|
228
|
+
| `create_invoice_sync.rb` | Issuing an invoice |
|
|
229
|
+
| `create_estimate_sync.rb` | Issuing a proforma + invoices-status |
|
|
230
|
+
| `create_payment_sync.rb` | Registering a `Chitanta` payment |
|
|
231
|
+
| `invoice_lifecycle_sync.rb` | Storno / cancel / restore / PDF |
|
|
232
|
+
| `list_series_sync.rb` | `GET /series` |
|
|
233
|
+
| `send_email_sync.rb` | `POST /document/send` with Base64 |
|
|
234
|
+
| `fiscal_receipt_sync.rb` | `Bon fiscal` with mixed cash/card payment |
|
|
235
|
+
| `taxes_and_stocks_sync.rb` | Concurrent `GET /tax` + `GET /stocks` via threads |
|
|
236
|
+
|
|
237
|
+
## Agent skills
|
|
238
|
+
|
|
239
|
+
This repo ships ready-to-import [pi](https://github.com/earendil-works/pi-coding-agent)
|
|
240
|
+
**skills** under [`skills/`](skills/) that teach coding agents how to use
|
|
241
|
+
the SDK. Each `SKILL.md` is a self-contained, copy-pasteable guide for one
|
|
242
|
+
area of the API:
|
|
243
|
+
|
|
244
|
+
| Skill | Covers |
|
|
245
|
+
|----------------------|-------------------------------------------------------------------------|
|
|
246
|
+
| `smartbill-invoices` | Invoices & proformas/estimates: create, storno, cancel, restore, PDF, payment status |
|
|
247
|
+
| `smartbill-payments` | Payments & fiscal receipts (`bon fiscal`): `POST /payment`, payment types, mixed cash/card, fiscal-printer text, delete |
|
|
248
|
+
| `smartbill-email` | Emailing a document (`POST /document/send`): base64 subject/body, invoice/proforma |
|
|
249
|
+
|
|
250
|
+
See [`skills/README.md`](skills/README.md) for how to import them into a pi
|
|
251
|
+
agent. The runnable scripts in [`examples/`](examples/) accompany these
|
|
252
|
+
skills.
|
|
253
|
+
|
|
254
|
+
## Disclaimer
|
|
255
|
+
|
|
256
|
+
This SDK was written by an AI agent (pi) as a Ruby port of the Python
|
|
257
|
+
`smartbill-rest-sdk`, which was itself generated from the official
|
|
258
|
+
`openapi.json` spec. The Ruby port is verified with a suite of 60 mocked
|
|
259
|
+
tests (using WebMock). Please have a human review it before issuing real
|
|
260
|
+
invoices — accountants work hard enough as it is.
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
The gem is available as open source under the terms of the
|
|
265
|
+
[MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "minitest/test_task"
|
|
5
|
+
|
|
6
|
+
Minitest::TestTask.create
|
|
7
|
+
|
|
8
|
+
require "rubocop/rake_task"
|
|
9
|
+
|
|
10
|
+
RuboCop::RakeTask.new
|
|
11
|
+
|
|
12
|
+
namespace :rbs do
|
|
13
|
+
desc "Validate the RBS signature files (syntax + consistency)"
|
|
14
|
+
task :validate do
|
|
15
|
+
sh "bundle exec rbs validate"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# `steep check` requires RBS signatures for dry-struct / dry-validation,
|
|
20
|
+
# which are not bundled with those gems; it is therefore opt-in rather than
|
|
21
|
+
# part of the default task. `rbs:validate` covers signature soundness.
|
|
22
|
+
task default: %i[test rubocop]
|