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
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]
data/Steepfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature "sig"
5
+ check "lib"
6
+ end