eqinvoice 1.1.0__tar.gz
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.
- eqinvoice-1.1.0/LICENSE +21 -0
- eqinvoice-1.1.0/PKG-INFO +303 -0
- eqinvoice-1.1.0/README.md +266 -0
- eqinvoice-1.1.0/pyproject.toml +85 -0
- eqinvoice-1.1.0/setup.cfg +4 -0
- eqinvoice-1.1.0/src/eqinvoice/__init__.py +97 -0
- eqinvoice-1.1.0/src/eqinvoice/access_key.py +46 -0
- eqinvoice-1.1.0/src/eqinvoice/document.py +203 -0
- eqinvoice-1.1.0/src/eqinvoice/errors.py +23 -0
- eqinvoice-1.1.0/src/eqinvoice/labels/es.json +65 -0
- eqinvoice-1.1.0/src/eqinvoice/model.py +155 -0
- eqinvoice-1.1.0/src/eqinvoice/pricing.py +39 -0
- eqinvoice-1.1.0/src/eqinvoice/protocol.py +108 -0
- eqinvoice-1.1.0/src/eqinvoice/py.typed +0 -0
- eqinvoice-1.1.0/src/eqinvoice/record.py +184 -0
- eqinvoice-1.1.0/src/eqinvoice/registry.py +149 -0
- eqinvoice-1.1.0/src/eqinvoice/ride/__init__.py +14 -0
- eqinvoice-1.1.0/src/eqinvoice/ride/labels.py +28 -0
- eqinvoice-1.1.0/src/eqinvoice/ride/logo.py +243 -0
- eqinvoice-1.1.0/src/eqinvoice/ride/pdf.py +548 -0
- eqinvoice-1.1.0/src/eqinvoice/schemas/NotaCredito_V1.1.0.xsd +584 -0
- eqinvoice-1.1.0/src/eqinvoice/schemas/factura_V1.1.0.xsd +961 -0
- eqinvoice-1.1.0/src/eqinvoice/schemas/xmldsig-core-schema.xsd +318 -0
- eqinvoice-1.1.0/src/eqinvoice/service.py +221 -0
- eqinvoice-1.1.0/src/eqinvoice/signature.py +276 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/PKG-INFO +303 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/SOURCES.txt +48 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/dependency_links.txt +1 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/entry_points.txt +2 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/requires.txt +14 -0
- eqinvoice-1.1.0/src/eqinvoice.egg-info/top_level.txt +2 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/__init__.py +0 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/book.py +205 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/desk.py +272 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/icon.svg +1 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/model.py +68 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/server.py +267 -0
- eqinvoice-1.1.0/src/eqinvoice_mcp/settings.py +28 -0
- eqinvoice-1.1.0/tests/test_access_key.py +33 -0
- eqinvoice-1.1.0/tests/test_book.py +52 -0
- eqinvoice-1.1.0/tests/test_desk.py +181 -0
- eqinvoice-1.1.0/tests/test_document.py +80 -0
- eqinvoice-1.1.0/tests/test_plugin.py +48 -0
- eqinvoice-1.1.0/tests/test_pricing.py +36 -0
- eqinvoice-1.1.0/tests/test_record.py +44 -0
- eqinvoice-1.1.0/tests/test_registry.py +98 -0
- eqinvoice-1.1.0/tests/test_ride.py +66 -0
- eqinvoice-1.1.0/tests/test_service.py +115 -0
- eqinvoice-1.1.0/tests/test_settings.py +24 -0
- eqinvoice-1.1.0/tests/test_signature.py +104 -0
eqinvoice-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jahirtrap
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
eqinvoice-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eqinvoice
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Electronic invoices and credit notes for Ecuador's SRI: build, sign, send, authorize and print, as a library or an MCP server.
|
|
5
|
+
Author: jahirtrap
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jahirxtrap/eqinvoice
|
|
8
|
+
Project-URL: Repository, https://github.com/jahirxtrap/eqinvoice
|
|
9
|
+
Project-URL: Issues, https://github.com/jahirxtrap/eqinvoice/issues
|
|
10
|
+
Keywords: ecuador,sri,electronic invoice,factura electronica,xades,mcp
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Natural Language :: Spanish
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: cryptography>=50.0
|
|
25
|
+
Requires-Dist: fonttools>=4.60
|
|
26
|
+
Requires-Dist: httpx>=0.28
|
|
27
|
+
Requires-Dist: lxml>=6.1
|
|
28
|
+
Requires-Dist: reportlab>=5.1
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: mcp>=2.3; extra == "mcp"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mcp>=2.3; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=2.4; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest>=9.1; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.17; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://raw.githubusercontent.com/jahirxtrap/eqinvoice/main/.github/assets/banner.png" alt="EQinvoice" width="760">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
Electronic invoices and credit notes for **Ecuador's SRI** (offline scheme 1.1.0): build the
|
|
43
|
+
voucher, sign it XAdES-BES with the issuer's .p12, send it, read the authorization back and print
|
|
44
|
+
the RIDE. Use it as a **library** inside your own system, or as an **MCP server** that lets an
|
|
45
|
+
assistant draft, preview, issue and follow one issuer's vouchers.
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<a href="https://pypi.org/project/eqinvoice/">PyPI</a> ·
|
|
49
|
+
<a href="#library">Library</a> ·
|
|
50
|
+
<a href="#claude-code-plugin">Claude Code plugin</a> ·
|
|
51
|
+
<a href="#mcp-server">MCP server</a>
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
[your system] ──values──> [eqinvoice] ──signed XML──> [SRI reception] ──> [SRI authorization]
|
|
56
|
+
[assistant] ──MCP────> [eqinvoice-mcp] ──> the book (clients, drafts, vouchers)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install eqinvoice # the library
|
|
65
|
+
pip install "eqinvoice[mcp]" # with the MCP server
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Python 3.11 or newer. In Claude Code, the [plugin](#claude-code-plugin) installs everything in one step.
|
|
69
|
+
|
|
70
|
+
## Capabilities
|
|
71
|
+
|
|
72
|
+
- **Vouchers**: invoices (factura) and credit notes (nota de crédito), checked against the SRI's own XSD before they leave.
|
|
73
|
+
- **Access key**: the 49-digit clave de acceso with its modulo 11 check digit, and the issue date on the SRI's clock.
|
|
74
|
+
- **Signature**: XAdES-BES with the issuer's .p12 and its password, refused with a reason when it is missing, unreadable or out of date.
|
|
75
|
+
- **SRI web services**: reception and authorization, in the test environment (celcer) or in production (cel).
|
|
76
|
+
- **Taxpayer registry**: a RUC looked up in the SRI's public registry (legal name, standing, regime, accounting duty, establishments), and the issuer its vouchers name.
|
|
77
|
+
- **Priced lines**: a line's subtotal and IVA worked out to the cent from its quantity, unit price, discount and the SRI's rate code.
|
|
78
|
+
- **Printed copy (RIDE)**: an A4 page or an 80 mm strip, with the SRI's full block of totals and the access key as a barcode (page) or a QR code (strip).
|
|
79
|
+
- **Logo**: a picture, or a mark and a name drawn as vectors, set beside each other as a lockup.
|
|
80
|
+
- **Labels**: the printed words are data. Spanish by default, as the SRI prints them; any file of the same shape replaces them.
|
|
81
|
+
- **Simulation**: a simulated signature, SRI and registry that answer on the spot, for development and tests.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Library
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datetime import UTC, datetime
|
|
89
|
+
from decimal import Decimal
|
|
90
|
+
from pathlib import Path
|
|
91
|
+
|
|
92
|
+
from eqinvoice import (
|
|
93
|
+
Buyer,
|
|
94
|
+
BuyerKind,
|
|
95
|
+
Environment,
|
|
96
|
+
Issuer,
|
|
97
|
+
Payment,
|
|
98
|
+
SriWebServices,
|
|
99
|
+
Voucher,
|
|
100
|
+
VoucherKind,
|
|
101
|
+
access_key,
|
|
102
|
+
issue_date,
|
|
103
|
+
price_line,
|
|
104
|
+
read,
|
|
105
|
+
read_credential,
|
|
106
|
+
sign_voucher,
|
|
107
|
+
)
|
|
108
|
+
from eqinvoice.ride import ImageLogo, Layout, render
|
|
109
|
+
|
|
110
|
+
issuer = Issuer(
|
|
111
|
+
legal_name="ACME COMERCIAL S.A.S.",
|
|
112
|
+
ruc="1790011223001",
|
|
113
|
+
head_office_address="AV. AMAZONAS N10-20, QUITO",
|
|
114
|
+
keeps_accounts=True,
|
|
115
|
+
)
|
|
116
|
+
credential = read_credential(Path("signature.p12"), "p12 password")
|
|
117
|
+
now = datetime.now(UTC)
|
|
118
|
+
issued_on = issue_date(now)
|
|
119
|
+
line = price_line("SKU-1", "Cafe molido (500 g)", Decimal("1"), Decimal("10.00"), "4")
|
|
120
|
+
voucher = Voucher(
|
|
121
|
+
kind=VoucherKind.INVOICE,
|
|
122
|
+
environment=Environment.TEST,
|
|
123
|
+
access_key=access_key(
|
|
124
|
+
issued_on, VoucherKind.INVOICE, issuer.ruc, Environment.TEST, "001", "001", 1
|
|
125
|
+
),
|
|
126
|
+
establishment="001",
|
|
127
|
+
point="001",
|
|
128
|
+
sequential=1,
|
|
129
|
+
establishment_address="AV. AMAZONAS N10-20, QUITO",
|
|
130
|
+
issued_on=issued_on,
|
|
131
|
+
buyer=Buyer(BuyerKind.CEDULA, "1710034065", "Ana Perez", "ana@example.com"),
|
|
132
|
+
lines=(line,),
|
|
133
|
+
payments=(Payment("01", Decimal("11.50")),),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
document = sign_voucher(voucher, issuer, credential, now)
|
|
137
|
+
sri = SriWebServices(Environment.TEST)
|
|
138
|
+
reception = sri.send(document)
|
|
139
|
+
answer = sri.authorize(voucher.access_key)
|
|
140
|
+
|
|
141
|
+
if answer is not None and answer.authorized:
|
|
142
|
+
issued = read(answer.record)
|
|
143
|
+
page = render(issued, Layout.PAGE, logo=ImageLogo(Path("logo.png")))
|
|
144
|
+
strip = render(issued, Layout.TICKET, logo=ImageLogo(Path("logo.png")))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- **What to keep**: `answer.record` is the authorized XML, the voucher with tax validity. Store it: `read(record)` gives the voucher back and `render` prints it whenever it is needed.
|
|
148
|
+
- **Still processing**: `authorize` gives `None` while the SRI has not answered yet; ask again later with the same access key.
|
|
149
|
+
- **Sent twice**: `reception.already_there` is true when the SRI already holds that access key, so its answer waits at the authorization service.
|
|
150
|
+
- **Numbering**: the establishment, the emission point and the sequential belong to the caller's system. A sequential is never reused, and a voucher sent again keeps its access key.
|
|
151
|
+
- **Signed documents**: `read_voucher(document)` gives back the voucher a signed or unsigned document carries.
|
|
152
|
+
|
|
153
|
+
<details>
|
|
154
|
+
<summary><b>Amounts and codes</b></summary>
|
|
155
|
+
|
|
156
|
+
- **Lines**: `subtotal` is the line before IVA (quantity times unit price minus discount) and `vat` its tax, both to two decimals. `price_line(code, description, quantity, unit_price, rate_code, discount)` works them out, rounding halves up. The voucher adds them up per rate.
|
|
157
|
+
- **IVA rate codes** (`rate_code`): the SRI's table, for example `"4"` for 15 %, `"0"` for 0 %, `"6"` not subject to IVA, `"7"` exempt.
|
|
158
|
+
- **Payment forms** (`Payment.form`): the SRI's table, for example `"01"` without the financial system (cash), `"16"` debit card, `"19"` credit card, `"20"` other with the financial system (transfer). Payments of the same form are added up.
|
|
159
|
+
- **Credit notes**: `modified=Modified(number, issued_on, reason)` names the invoice they correct; credit notes carry no payments.
|
|
160
|
+
- **Final consumer**: `Buyer(BuyerKind.FINAL_CONSUMER)`. The voucher writes the SRI's own name and number for it.
|
|
161
|
+
|
|
162
|
+
</details>
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary><b>When something fails</b></summary>
|
|
166
|
+
|
|
167
|
+
- **`SriUnavailable`**: the SRI could not be reached or failed on its side. Nothing is lost: send the same document again later.
|
|
168
|
+
- **`VoucherInvalid`**: the voucher breaks the SRI's schema (the message names every reason) or the record holds no voucher. Fix the data; sending it would only be returned.
|
|
169
|
+
- **`CredentialRefused`**: the signature cannot sign. `problem` says why: `missing`, `unreadable` (wrong password or not a .p12) or `out_of_date`.
|
|
170
|
+
- **Turned down**: `answer.authorized` is false and `answer.messages` carry the SRI's codes and texts, as it wrote them.
|
|
171
|
+
|
|
172
|
+
</details>
|
|
173
|
+
|
|
174
|
+
### Taxpayer registry
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from eqinvoice import SriRegistry
|
|
178
|
+
|
|
179
|
+
taxpayer = SriRegistry().find("1791234567001")
|
|
180
|
+
if taxpayer is not None and taxpayer.active:
|
|
181
|
+
issuer = taxpayer.issuer(trade_name="Acme Shop")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **Nobody holds it**: `find` gives `None`. A registry that cannot be reached raises `SriUnavailable`.
|
|
185
|
+
- **The issuer**: `taxpayer.issuer()` names the head office address the registry lists. A withholding agent or a special taxpayer passes its resolution or number (`withholding_agent=`, `special_taxpayer=`), which the registry does not publish.
|
|
186
|
+
|
|
187
|
+
### Logo
|
|
188
|
+
|
|
189
|
+
- **A mark and a name**: `MarkLogo(mark=Path("mark.svg"), name=Path("name.svg"), font=Path("Brand.ttf"))`. The mark is an SVG of rectangles and straight paths; the name is an SVG `<text>` whose runs carry their colours (a `<tspan>` for a second colour), set in the font given. They sit as a lockup: for a mark 28 high the name is 17 and the gap 8, the proportion `name_size` and `gap` change, and the name's own glyphs (ascenders and descenders included) are centred on the mark.
|
|
190
|
+
- **A picture**: `ImageLogo(Path("logo.png"))`, drawn at the logo's height keeping its proportions. `ImageLogo(path, one_ink_path=Path("logo-black.png"))` gives the thermal strip its own black version.
|
|
191
|
+
- **No logo**: leave `logo` out and the issuer's box takes the space.
|
|
192
|
+
|
|
193
|
+
On the A4 page the logo goes above the issuer's box, in colour. On the strip it goes on top, in one ink.
|
|
194
|
+
|
|
195
|
+
### Labels
|
|
196
|
+
|
|
197
|
+
`default_labels()` holds the Spanish words the SRI prints. To change them, copy `src/eqinvoice/labels/es.json`, edit the values and pass `labels=read_labels(Path("labels.json"))` to `render`. The keys stay as they are.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Claude Code plugin
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
/plugin install eqinvoice --marketplace jahirxtrap/eqinvoice
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Or add the marketplace once and install it from there:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
claude plugin marketplace add jahirxtrap/eqinvoice
|
|
211
|
+
claude plugin install eqinvoice@eqinvoice
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
On enabling it, Claude Code asks for the book folder, the .p12 signature and its password; the password goes to the system's secure storage. The plugin starts the [MCP server](#mcp-server) with `uvx` from PyPI (it needs [uv](https://docs.astral.sh/uv/)): the first start installs it, later ones take a couple of seconds. Its tools show up as `mcp__plugin_eqinvoice_eqinvoice__*`.
|
|
215
|
+
|
|
216
|
+
## MCP server
|
|
217
|
+
|
|
218
|
+
`eqinvoice-mcp` serves one issuer over stdio. It keeps everything in a folder, the book, and signs with the issuer's .p12. Without the plugin, register it by hand:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
claude mcp add --scope user eqinvoice \
|
|
222
|
+
-e EQINVOICE_BOOK=/path/to/book \
|
|
223
|
+
-e EQINVOICE_P12=/path/to/signature.p12 \
|
|
224
|
+
-e EQINVOICE_P12_PASSWORD=... \
|
|
225
|
+
-- uvx --from "eqinvoice[mcp]" eqinvoice-mcp
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- **Issuer**: `configure_issuer` takes a RUC, the environment (`test` or `production`), the establishment and the emission point. The legal name, the address and the accounting duty come from the SRI's registry.
|
|
229
|
+
- **Clients**: `register_client` keeps a buyer and its email. A RUC is named as the registry holds it; a cédula or a passport brings its name.
|
|
230
|
+
- **Drafts first**: every voucher starts as a draft with the voucher it would become and a printed preview (a PDF). Nothing reaches the SRI until the draft is issued.
|
|
231
|
+
- **Issuing**: `issue_draft` numbers the draft, signs it, sends it and files it as the SRI leaves it: `authorized` (with its record and its printed copy), `refused` (with the SRI's messages) or `pending`, which `check_voucher` asks for again.
|
|
232
|
+
- **Numbering**: each environment, kind, establishment and point numbers its own series, and a number is taken for good once a voucher is signed with it.
|
|
233
|
+
|
|
234
|
+
<details>
|
|
235
|
+
<summary><b>Tools</b></summary>
|
|
236
|
+
|
|
237
|
+
| Tool | What it does |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `look_up_taxpayer` | A RUC as the SRI's registry holds it |
|
|
240
|
+
| `configure_issuer` | Who issues, from which establishment and point, in which environment |
|
|
241
|
+
| `show_issuer` | The issuer as it is configured |
|
|
242
|
+
| `register_client`, `list_clients`, `forget_client` | The clients in the book |
|
|
243
|
+
| `draft_invoice` | An invoice for a client, with its printed preview |
|
|
244
|
+
| `draft_credit_note` | A credit note against an authorized invoice, whole or by lines |
|
|
245
|
+
| `list_drafts`, `discard_draft` | The drafts waiting to be issued |
|
|
246
|
+
| `issue_draft` | The approved draft numbered, signed and sent to the SRI |
|
|
247
|
+
| `list_vouchers` | Every voucher numbered in the current environment |
|
|
248
|
+
| `check_voucher` | A pending voucher sent again and asked for |
|
|
249
|
+
|
|
250
|
+
</details>
|
|
251
|
+
|
|
252
|
+
<details>
|
|
253
|
+
<summary><b>The book</b></summary>
|
|
254
|
+
|
|
255
|
+
| Path | What it holds |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `profile.json` | The issuer, its establishment and point, and the environment |
|
|
258
|
+
| `clients/<identification>.json` | One client each |
|
|
259
|
+
| `drafts/<id>.json`, `drafts/<id>.pdf` | A draft and its printed preview |
|
|
260
|
+
| `vouchers/<environment>/<kind>/<number>/` | `signed.xml`, then `authorized.xml` and `printed.pdf`, or `refused.json` |
|
|
261
|
+
| `brand/` | The logo of the printed copies: `mark.svg`, `name.svg` and a `.ttf`, or `logo.png`. An optional `lockup.json` (`{"name_size": 0.76}`) sizes the name or the gap for this brand |
|
|
262
|
+
|
|
263
|
+
</details>
|
|
264
|
+
|
|
265
|
+
<details>
|
|
266
|
+
<summary><b>Environment variables</b></summary>
|
|
267
|
+
|
|
268
|
+
| Variable | What it does |
|
|
269
|
+
|---|---|
|
|
270
|
+
| `EQINVOICE_BOOK` | The folder the server keeps the book in |
|
|
271
|
+
| `EQINVOICE_P12` | The issuer's .p12 signature |
|
|
272
|
+
| `EQINVOICE_P12_PASSWORD` | Its password |
|
|
273
|
+
|
|
274
|
+
</details>
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
python3.11 -m venv .venv
|
|
282
|
+
.venv/bin/pip install -e ".[dev]"
|
|
283
|
+
.venv/bin/ruff check src tests scripts
|
|
284
|
+
.venv/bin/mypy
|
|
285
|
+
.venv/bin/pytest
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Two scripts drive the live services:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
EQINVOICE_P12=signature.p12 EQINVOICE_P12_PASSWORD=... \
|
|
292
|
+
.venv/bin/python scripts/smoke_sri.py setup.json out
|
|
293
|
+
.venv/bin/python scripts/smoke_registry.py 1791234567001
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
- **`smoke_sri.py`**: a real round trip against the SRI's test environment. It signs a $1.00 invoice to a final consumer, sends it, waits for the authorization, reads it back and prints both layouts. `setup.json` holds `issuer` (the `Issuer` fields), `establishment`, `point` and `establishment_address`; use an emission point no other system numbers with.
|
|
297
|
+
- **`smoke_registry.py`**: a RUC looked up in the SRI's live registry, with its establishments and the issuer it would be.
|
|
298
|
+
|
|
299
|
+
A tag (`1.2.0`) publishes that version to PyPI and opens its release with `CHANGELOG.md`. The version lives in `pyproject.toml` and, the same, in the plugin's manifest, its marketplace entry and the package its `.mcp.json` launches; `tests/test_plugin.py` keeps them in agreement.
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/jahirxtrap/eqinvoice/main/.github/assets/banner.png" alt="EQinvoice" width="760">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
Electronic invoices and credit notes for **Ecuador's SRI** (offline scheme 1.1.0): build the
|
|
6
|
+
voucher, sign it XAdES-BES with the issuer's .p12, send it, read the authorization back and print
|
|
7
|
+
the RIDE. Use it as a **library** inside your own system, or as an **MCP server** that lets an
|
|
8
|
+
assistant draft, preview, issue and follow one issuer's vouchers.
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://pypi.org/project/eqinvoice/">PyPI</a> ·
|
|
12
|
+
<a href="#library">Library</a> ·
|
|
13
|
+
<a href="#claude-code-plugin">Claude Code plugin</a> ·
|
|
14
|
+
<a href="#mcp-server">MCP server</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
[your system] ──values──> [eqinvoice] ──signed XML──> [SRI reception] ──> [SRI authorization]
|
|
19
|
+
[assistant] ──MCP────> [eqinvoice-mcp] ──> the book (clients, drafts, vouchers)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install eqinvoice # the library
|
|
28
|
+
pip install "eqinvoice[mcp]" # with the MCP server
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Python 3.11 or newer. In Claude Code, the [plugin](#claude-code-plugin) installs everything in one step.
|
|
32
|
+
|
|
33
|
+
## Capabilities
|
|
34
|
+
|
|
35
|
+
- **Vouchers**: invoices (factura) and credit notes (nota de crédito), checked against the SRI's own XSD before they leave.
|
|
36
|
+
- **Access key**: the 49-digit clave de acceso with its modulo 11 check digit, and the issue date on the SRI's clock.
|
|
37
|
+
- **Signature**: XAdES-BES with the issuer's .p12 and its password, refused with a reason when it is missing, unreadable or out of date.
|
|
38
|
+
- **SRI web services**: reception and authorization, in the test environment (celcer) or in production (cel).
|
|
39
|
+
- **Taxpayer registry**: a RUC looked up in the SRI's public registry (legal name, standing, regime, accounting duty, establishments), and the issuer its vouchers name.
|
|
40
|
+
- **Priced lines**: a line's subtotal and IVA worked out to the cent from its quantity, unit price, discount and the SRI's rate code.
|
|
41
|
+
- **Printed copy (RIDE)**: an A4 page or an 80 mm strip, with the SRI's full block of totals and the access key as a barcode (page) or a QR code (strip).
|
|
42
|
+
- **Logo**: a picture, or a mark and a name drawn as vectors, set beside each other as a lockup.
|
|
43
|
+
- **Labels**: the printed words are data. Spanish by default, as the SRI prints them; any file of the same shape replaces them.
|
|
44
|
+
- **Simulation**: a simulated signature, SRI and registry that answer on the spot, for development and tests.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Library
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from datetime import UTC, datetime
|
|
52
|
+
from decimal import Decimal
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
from eqinvoice import (
|
|
56
|
+
Buyer,
|
|
57
|
+
BuyerKind,
|
|
58
|
+
Environment,
|
|
59
|
+
Issuer,
|
|
60
|
+
Payment,
|
|
61
|
+
SriWebServices,
|
|
62
|
+
Voucher,
|
|
63
|
+
VoucherKind,
|
|
64
|
+
access_key,
|
|
65
|
+
issue_date,
|
|
66
|
+
price_line,
|
|
67
|
+
read,
|
|
68
|
+
read_credential,
|
|
69
|
+
sign_voucher,
|
|
70
|
+
)
|
|
71
|
+
from eqinvoice.ride import ImageLogo, Layout, render
|
|
72
|
+
|
|
73
|
+
issuer = Issuer(
|
|
74
|
+
legal_name="ACME COMERCIAL S.A.S.",
|
|
75
|
+
ruc="1790011223001",
|
|
76
|
+
head_office_address="AV. AMAZONAS N10-20, QUITO",
|
|
77
|
+
keeps_accounts=True,
|
|
78
|
+
)
|
|
79
|
+
credential = read_credential(Path("signature.p12"), "p12 password")
|
|
80
|
+
now = datetime.now(UTC)
|
|
81
|
+
issued_on = issue_date(now)
|
|
82
|
+
line = price_line("SKU-1", "Cafe molido (500 g)", Decimal("1"), Decimal("10.00"), "4")
|
|
83
|
+
voucher = Voucher(
|
|
84
|
+
kind=VoucherKind.INVOICE,
|
|
85
|
+
environment=Environment.TEST,
|
|
86
|
+
access_key=access_key(
|
|
87
|
+
issued_on, VoucherKind.INVOICE, issuer.ruc, Environment.TEST, "001", "001", 1
|
|
88
|
+
),
|
|
89
|
+
establishment="001",
|
|
90
|
+
point="001",
|
|
91
|
+
sequential=1,
|
|
92
|
+
establishment_address="AV. AMAZONAS N10-20, QUITO",
|
|
93
|
+
issued_on=issued_on,
|
|
94
|
+
buyer=Buyer(BuyerKind.CEDULA, "1710034065", "Ana Perez", "ana@example.com"),
|
|
95
|
+
lines=(line,),
|
|
96
|
+
payments=(Payment("01", Decimal("11.50")),),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
document = sign_voucher(voucher, issuer, credential, now)
|
|
100
|
+
sri = SriWebServices(Environment.TEST)
|
|
101
|
+
reception = sri.send(document)
|
|
102
|
+
answer = sri.authorize(voucher.access_key)
|
|
103
|
+
|
|
104
|
+
if answer is not None and answer.authorized:
|
|
105
|
+
issued = read(answer.record)
|
|
106
|
+
page = render(issued, Layout.PAGE, logo=ImageLogo(Path("logo.png")))
|
|
107
|
+
strip = render(issued, Layout.TICKET, logo=ImageLogo(Path("logo.png")))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- **What to keep**: `answer.record` is the authorized XML, the voucher with tax validity. Store it: `read(record)` gives the voucher back and `render` prints it whenever it is needed.
|
|
111
|
+
- **Still processing**: `authorize` gives `None` while the SRI has not answered yet; ask again later with the same access key.
|
|
112
|
+
- **Sent twice**: `reception.already_there` is true when the SRI already holds that access key, so its answer waits at the authorization service.
|
|
113
|
+
- **Numbering**: the establishment, the emission point and the sequential belong to the caller's system. A sequential is never reused, and a voucher sent again keeps its access key.
|
|
114
|
+
- **Signed documents**: `read_voucher(document)` gives back the voucher a signed or unsigned document carries.
|
|
115
|
+
|
|
116
|
+
<details>
|
|
117
|
+
<summary><b>Amounts and codes</b></summary>
|
|
118
|
+
|
|
119
|
+
- **Lines**: `subtotal` is the line before IVA (quantity times unit price minus discount) and `vat` its tax, both to two decimals. `price_line(code, description, quantity, unit_price, rate_code, discount)` works them out, rounding halves up. The voucher adds them up per rate.
|
|
120
|
+
- **IVA rate codes** (`rate_code`): the SRI's table, for example `"4"` for 15 %, `"0"` for 0 %, `"6"` not subject to IVA, `"7"` exempt.
|
|
121
|
+
- **Payment forms** (`Payment.form`): the SRI's table, for example `"01"` without the financial system (cash), `"16"` debit card, `"19"` credit card, `"20"` other with the financial system (transfer). Payments of the same form are added up.
|
|
122
|
+
- **Credit notes**: `modified=Modified(number, issued_on, reason)` names the invoice they correct; credit notes carry no payments.
|
|
123
|
+
- **Final consumer**: `Buyer(BuyerKind.FINAL_CONSUMER)`. The voucher writes the SRI's own name and number for it.
|
|
124
|
+
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary><b>When something fails</b></summary>
|
|
129
|
+
|
|
130
|
+
- **`SriUnavailable`**: the SRI could not be reached or failed on its side. Nothing is lost: send the same document again later.
|
|
131
|
+
- **`VoucherInvalid`**: the voucher breaks the SRI's schema (the message names every reason) or the record holds no voucher. Fix the data; sending it would only be returned.
|
|
132
|
+
- **`CredentialRefused`**: the signature cannot sign. `problem` says why: `missing`, `unreadable` (wrong password or not a .p12) or `out_of_date`.
|
|
133
|
+
- **Turned down**: `answer.authorized` is false and `answer.messages` carry the SRI's codes and texts, as it wrote them.
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
### Taxpayer registry
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from eqinvoice import SriRegistry
|
|
141
|
+
|
|
142
|
+
taxpayer = SriRegistry().find("1791234567001")
|
|
143
|
+
if taxpayer is not None and taxpayer.active:
|
|
144
|
+
issuer = taxpayer.issuer(trade_name="Acme Shop")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- **Nobody holds it**: `find` gives `None`. A registry that cannot be reached raises `SriUnavailable`.
|
|
148
|
+
- **The issuer**: `taxpayer.issuer()` names the head office address the registry lists. A withholding agent or a special taxpayer passes its resolution or number (`withholding_agent=`, `special_taxpayer=`), which the registry does not publish.
|
|
149
|
+
|
|
150
|
+
### Logo
|
|
151
|
+
|
|
152
|
+
- **A mark and a name**: `MarkLogo(mark=Path("mark.svg"), name=Path("name.svg"), font=Path("Brand.ttf"))`. The mark is an SVG of rectangles and straight paths; the name is an SVG `<text>` whose runs carry their colours (a `<tspan>` for a second colour), set in the font given. They sit as a lockup: for a mark 28 high the name is 17 and the gap 8, the proportion `name_size` and `gap` change, and the name's own glyphs (ascenders and descenders included) are centred on the mark.
|
|
153
|
+
- **A picture**: `ImageLogo(Path("logo.png"))`, drawn at the logo's height keeping its proportions. `ImageLogo(path, one_ink_path=Path("logo-black.png"))` gives the thermal strip its own black version.
|
|
154
|
+
- **No logo**: leave `logo` out and the issuer's box takes the space.
|
|
155
|
+
|
|
156
|
+
On the A4 page the logo goes above the issuer's box, in colour. On the strip it goes on top, in one ink.
|
|
157
|
+
|
|
158
|
+
### Labels
|
|
159
|
+
|
|
160
|
+
`default_labels()` holds the Spanish words the SRI prints. To change them, copy `src/eqinvoice/labels/es.json`, edit the values and pass `labels=read_labels(Path("labels.json"))` to `render`. The keys stay as they are.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Claude Code plugin
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
/plugin install eqinvoice --marketplace jahirxtrap/eqinvoice
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or add the marketplace once and install it from there:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
claude plugin marketplace add jahirxtrap/eqinvoice
|
|
174
|
+
claude plugin install eqinvoice@eqinvoice
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
On enabling it, Claude Code asks for the book folder, the .p12 signature and its password; the password goes to the system's secure storage. The plugin starts the [MCP server](#mcp-server) with `uvx` from PyPI (it needs [uv](https://docs.astral.sh/uv/)): the first start installs it, later ones take a couple of seconds. Its tools show up as `mcp__plugin_eqinvoice_eqinvoice__*`.
|
|
178
|
+
|
|
179
|
+
## MCP server
|
|
180
|
+
|
|
181
|
+
`eqinvoice-mcp` serves one issuer over stdio. It keeps everything in a folder, the book, and signs with the issuer's .p12. Without the plugin, register it by hand:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
claude mcp add --scope user eqinvoice \
|
|
185
|
+
-e EQINVOICE_BOOK=/path/to/book \
|
|
186
|
+
-e EQINVOICE_P12=/path/to/signature.p12 \
|
|
187
|
+
-e EQINVOICE_P12_PASSWORD=... \
|
|
188
|
+
-- uvx --from "eqinvoice[mcp]" eqinvoice-mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- **Issuer**: `configure_issuer` takes a RUC, the environment (`test` or `production`), the establishment and the emission point. The legal name, the address and the accounting duty come from the SRI's registry.
|
|
192
|
+
- **Clients**: `register_client` keeps a buyer and its email. A RUC is named as the registry holds it; a cédula or a passport brings its name.
|
|
193
|
+
- **Drafts first**: every voucher starts as a draft with the voucher it would become and a printed preview (a PDF). Nothing reaches the SRI until the draft is issued.
|
|
194
|
+
- **Issuing**: `issue_draft` numbers the draft, signs it, sends it and files it as the SRI leaves it: `authorized` (with its record and its printed copy), `refused` (with the SRI's messages) or `pending`, which `check_voucher` asks for again.
|
|
195
|
+
- **Numbering**: each environment, kind, establishment and point numbers its own series, and a number is taken for good once a voucher is signed with it.
|
|
196
|
+
|
|
197
|
+
<details>
|
|
198
|
+
<summary><b>Tools</b></summary>
|
|
199
|
+
|
|
200
|
+
| Tool | What it does |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `look_up_taxpayer` | A RUC as the SRI's registry holds it |
|
|
203
|
+
| `configure_issuer` | Who issues, from which establishment and point, in which environment |
|
|
204
|
+
| `show_issuer` | The issuer as it is configured |
|
|
205
|
+
| `register_client`, `list_clients`, `forget_client` | The clients in the book |
|
|
206
|
+
| `draft_invoice` | An invoice for a client, with its printed preview |
|
|
207
|
+
| `draft_credit_note` | A credit note against an authorized invoice, whole or by lines |
|
|
208
|
+
| `list_drafts`, `discard_draft` | The drafts waiting to be issued |
|
|
209
|
+
| `issue_draft` | The approved draft numbered, signed and sent to the SRI |
|
|
210
|
+
| `list_vouchers` | Every voucher numbered in the current environment |
|
|
211
|
+
| `check_voucher` | A pending voucher sent again and asked for |
|
|
212
|
+
|
|
213
|
+
</details>
|
|
214
|
+
|
|
215
|
+
<details>
|
|
216
|
+
<summary><b>The book</b></summary>
|
|
217
|
+
|
|
218
|
+
| Path | What it holds |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `profile.json` | The issuer, its establishment and point, and the environment |
|
|
221
|
+
| `clients/<identification>.json` | One client each |
|
|
222
|
+
| `drafts/<id>.json`, `drafts/<id>.pdf` | A draft and its printed preview |
|
|
223
|
+
| `vouchers/<environment>/<kind>/<number>/` | `signed.xml`, then `authorized.xml` and `printed.pdf`, or `refused.json` |
|
|
224
|
+
| `brand/` | The logo of the printed copies: `mark.svg`, `name.svg` and a `.ttf`, or `logo.png`. An optional `lockup.json` (`{"name_size": 0.76}`) sizes the name or the gap for this brand |
|
|
225
|
+
|
|
226
|
+
</details>
|
|
227
|
+
|
|
228
|
+
<details>
|
|
229
|
+
<summary><b>Environment variables</b></summary>
|
|
230
|
+
|
|
231
|
+
| Variable | What it does |
|
|
232
|
+
|---|---|
|
|
233
|
+
| `EQINVOICE_BOOK` | The folder the server keeps the book in |
|
|
234
|
+
| `EQINVOICE_P12` | The issuer's .p12 signature |
|
|
235
|
+
| `EQINVOICE_P12_PASSWORD` | Its password |
|
|
236
|
+
|
|
237
|
+
</details>
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
python3.11 -m venv .venv
|
|
245
|
+
.venv/bin/pip install -e ".[dev]"
|
|
246
|
+
.venv/bin/ruff check src tests scripts
|
|
247
|
+
.venv/bin/mypy
|
|
248
|
+
.venv/bin/pytest
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Two scripts drive the live services:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
EQINVOICE_P12=signature.p12 EQINVOICE_P12_PASSWORD=... \
|
|
255
|
+
.venv/bin/python scripts/smoke_sri.py setup.json out
|
|
256
|
+
.venv/bin/python scripts/smoke_registry.py 1791234567001
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
- **`smoke_sri.py`**: a real round trip against the SRI's test environment. It signs a $1.00 invoice to a final consumer, sends it, waits for the authorization, reads it back and prints both layouts. `setup.json` holds `issuer` (the `Issuer` fields), `establishment`, `point` and `establishment_address`; use an emission point no other system numbers with.
|
|
260
|
+
- **`smoke_registry.py`**: a RUC looked up in the SRI's live registry, with its establishments and the issuer it would be.
|
|
261
|
+
|
|
262
|
+
A tag (`1.2.0`) publishes that version to PyPI and opens its release with `CHANGELOG.md`. The version lives in `pyproject.toml` and, the same, in the plugin's manifest, its marketplace entry and the package its `.mcp.json` launches; `tests/test_plugin.py` keeps them in agreement.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "eqinvoice"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "Electronic invoices and credit notes for Ecuador's SRI: build, sign, send, authorize and print, as a library or an MCP server."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "jahirtrap" }]
|
|
10
|
+
keywords = ["ecuador", "sri", "electronic invoice", "factura electronica", "xades", "mcp"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 5 - Production/Stable",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Natural Language :: Spanish",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"cryptography>=50.0",
|
|
25
|
+
"fonttools>=4.60",
|
|
26
|
+
"httpx>=0.28",
|
|
27
|
+
"lxml>=6.1",
|
|
28
|
+
"reportlab>=5.1",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
mcp = [
|
|
33
|
+
"mcp>=2.3",
|
|
34
|
+
]
|
|
35
|
+
dev = [
|
|
36
|
+
"mcp>=2.3",
|
|
37
|
+
"mypy>=2.4",
|
|
38
|
+
"pytest>=9.1",
|
|
39
|
+
"ruff>=0.17",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/jahirxtrap/eqinvoice"
|
|
44
|
+
Repository = "https://github.com/jahirxtrap/eqinvoice"
|
|
45
|
+
Issues = "https://github.com/jahirxtrap/eqinvoice/issues"
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
eqinvoice-mcp = "eqinvoice_mcp.server:main"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["setuptools>=80.0"]
|
|
52
|
+
build-backend = "setuptools.build_meta"
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.package-data]
|
|
58
|
+
eqinvoice = ["py.typed", "schemas/*.xsd", "labels/*.json"]
|
|
59
|
+
eqinvoice_mcp = ["icon.svg"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
addopts = "--strict-markers -q"
|
|
64
|
+
|
|
65
|
+
[tool.mypy]
|
|
66
|
+
python_version = "3.11"
|
|
67
|
+
files = ["src", "tests", "scripts"]
|
|
68
|
+
ignore_missing_imports = true
|
|
69
|
+
no_implicit_optional = true
|
|
70
|
+
warn_redundant_casts = true
|
|
71
|
+
warn_unused_ignores = true
|
|
72
|
+
warn_unreachable = true
|
|
73
|
+
check_untyped_defs = true
|
|
74
|
+
disallow_untyped_defs = true
|
|
75
|
+
|
|
76
|
+
[[tool.mypy.overrides]]
|
|
77
|
+
module = ["tests.*"]
|
|
78
|
+
disallow_untyped_defs = false
|
|
79
|
+
|
|
80
|
+
[tool.ruff]
|
|
81
|
+
line-length = 100
|
|
82
|
+
target-version = "py311"
|
|
83
|
+
|
|
84
|
+
[tool.ruff.lint]
|
|
85
|
+
select = ["E", "F", "I", "UP", "B"]
|