quints 0.2.0__tar.gz → 0.4.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.
- {quints-0.2.0 → quints-0.4.0}/.gitignore +1 -0
- {quints-0.2.0 → quints-0.4.0}/PKG-INFO +5 -3
- {quints-0.2.0 → quints-0.4.0}/README.md +3 -1
- quints-0.4.0/examples/.gitignore +8 -0
- quints-0.4.0/examples/AGENTS.md +120 -0
- quints-0.4.0/examples/CLAUDE.md +1 -0
- quints-0.4.0/examples/invoicing/acme-2026-07.yaml +16 -0
- quints-0.4.0/examples/invoicing/customers.yaml +16 -0
- quints-0.4.0/examples/invoicing/globex-2026-08.yaml +14 -0
- quints-0.4.0/examples/invoicing/issuer.yaml +17 -0
- {quints-0.2.0 → quints-0.4.0}/pyproject.toml +2 -2
- quints-0.4.0/src/quints/__init__.py +7 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/cli.py +73 -4
- {quints-0.2.0 → quints-0.4.0}/src/quints/config.py +5 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/init.py +299 -17
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/model.py +15 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/prices.py +50 -3
- quints-0.4.0/tests/test_docs.py +137 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_init.py +182 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_prices_sync.py +21 -1
- quints-0.2.0/examples/.gitignore +0 -8
- quints-0.2.0/examples/AGENTS.md +0 -56
- quints-0.2.0/examples/invoicing/acme-2026-07.yaml +0 -19
- quints-0.2.0/examples/invoicing/issuer.yaml +0 -12
- quints-0.2.0/src/quints/__init__.py +0 -3
- quints-0.2.0/tests/test_docs.py +0 -48
- {quints-0.2.0 → quints-0.4.0}/LICENSE +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/accounts.bean +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/answers.toml +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/books/2026.bean +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/commodities.bean +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/documents/.gitkeep +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/inbox/.gitkeep +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/main.bean +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/prices.bean +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/pyproject.toml +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/quints.toml +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/staging/.gitkeep +0 -0
- {quints-0.2.0 → quints-0.4.0}/examples/statements/ubs-2026.mt940 +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/fava/__init__.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/fava/templates/QuintDashboard.html +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/fx.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/importing.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/inbox.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/__init__.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/draft.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/labels.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/qr.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/render.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/template.typ +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/vatid.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/verify.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/kmu.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/ledger.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/match.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/mwst.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/plugins/__init__.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/plugins/kmu.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/receivables.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/report.typ +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/report_pdf.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/settlement.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/ui.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/src/quints/vat.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/fixtures/transactions.mt940 +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_cli_json.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_config.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_fava_ext.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_fx.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_importing.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_inbox_match.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_invoice.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_kmu_plugin.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_kmu_report.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_ledger.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_mwst.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_receivables.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_report_pdf.py +0 -0
- {quints-0.2.0 → quints-0.4.0}/tests/test_settlement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quints
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Swiss VAT & accounting toolkit for plain-text (beancount) books: MWST reports and quarterly settlement, Bezugsteuer helpers, BAZG daily FX rates, QR-bill invoicing, KMU statutory statements, and statement importers (UBS MT940, Wise, Stripe) that draft into a staging area
|
|
5
5
|
License: GPL-2.0-only
|
|
6
6
|
License-File: LICENSE
|
|
@@ -11,7 +11,7 @@ Requires-Dist: beancount>=3.2.0
|
|
|
11
11
|
Requires-Dist: beangulp-mt940>=0.1.0
|
|
12
12
|
Requires-Dist: beangulp-stripe>=0.1.0
|
|
13
13
|
Requires-Dist: beangulp-wise[sca]>=0.1.0
|
|
14
|
-
Requires-Dist: beanprice-bazg>=0.
|
|
14
|
+
Requires-Dist: beanprice-bazg>=0.2.0
|
|
15
15
|
Requires-Dist: fava>=1.30
|
|
16
16
|
Requires-Dist: pydantic>=2.7
|
|
17
17
|
Requires-Dist: python-stdnum>=1.20
|
|
@@ -27,7 +27,9 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
|
|
28
28
|
Swiss VAT & accounting toolkit for plain-text ([beancount](https://github.com/beancount/beancount)) books.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
Everything a Swiss micro-company (GmbH, AG, or Einzelfirma) needs on top of beancount + Fava:
|
|
31
33
|
|
|
32
34
|
- **MWST**: quarterly VAT report mapped to the ESTV form Ziffern, settlement
|
|
33
35
|
transactions, VAT status, and Bezugsteuer (reverse-charge, Art. 45 ff. MWSTG)
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Swiss VAT & accounting toolkit for plain-text ([beancount](https://github.com/beancount/beancount)) books.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Everything a Swiss micro-company (GmbH, AG, or Einzelfirma) needs on top of beancount + Fava:
|
|
6
8
|
|
|
7
9
|
- **MWST**: quarterly VAT report mapped to the ESTV form Ziffern, settlement
|
|
8
10
|
transactions, VAT status, and Bezugsteuer (reverse-charge, Art. 45 ff. MWSTG)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Working on Example GmbH's books with an AI agent
|
|
2
|
+
|
|
3
|
+
These are plain-text ([beancount](https://beancount.github.io)) books managed
|
|
4
|
+
with [`quints`](https://github.com/sealambda/quints). **`quints` is a
|
|
5
|
+
deterministic tool — you drive it, it never calls a model.** Your job is to
|
|
6
|
+
extend and maintain the ledger; `quints` validates and reports on it.
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
`uv sync` once — it installs quints, which brings beancount and fava along.
|
|
11
|
+
Then `uv run quints check` (or activate the venv and call `quints` and the
|
|
12
|
+
standard beancount tools directly).
|
|
13
|
+
|
|
14
|
+
This project is a git repository; `quints init` committed the pristine
|
|
15
|
+
scaffold. Work in reviewable steps: `git diff` before moving drafts into
|
|
16
|
+
`books/`, commit once `quints check` passes — the history is the audit trail.
|
|
17
|
+
|
|
18
|
+
## Layout
|
|
19
|
+
|
|
20
|
+
- `main.bean` — options, plugins, includes; the entry point every tool loads.
|
|
21
|
+
`plugin "quints.plugins.kmu"` is enabled: every `*:CH:GmbH:*` account
|
|
22
|
+
**must** be opened with a four-digit `kmu:` code (Swiss KMU Kontenrahmen).
|
|
23
|
+
- `accounts.bean` — the chart of accounts (all `open` directives).
|
|
24
|
+
- `books/2026.bean` — transactions, one file per fiscal year. `main.bean`
|
|
25
|
+
includes `books/*.bean`, so a new year just needs a new file.
|
|
26
|
+
- `commodities.bean` — currencies; `prices.bean` — FX rates, refresh with
|
|
27
|
+
`quints prices sync`.
|
|
28
|
+
- `quints.toml` — entity config (name, legal form, VAT, importer rules). VAT
|
|
29
|
+
*rates* are law and live in code, not here.
|
|
30
|
+
- `staging/` — importer drafts land here (git-ignored, transient).
|
|
31
|
+
- `inbox/` — incoming source documents, not yet filed.
|
|
32
|
+
- `documents/` — filed documents, mirroring the account tree as
|
|
33
|
+
`documents/<Account/Tree>/YYYY-MM-DD.payee.description.pdf`. Committed:
|
|
34
|
+
the ledger links to these files (`fava.plugins.link_documents`).
|
|
35
|
+
- `invoicing/` — issuer identity (`issuer.yaml`), customer registry
|
|
36
|
+
(`customers.yaml`), one YAML per issued invoice.
|
|
37
|
+
|
|
38
|
+
## Extending the chart of accounts (the part that needs judgement)
|
|
39
|
+
|
|
40
|
+
Add income/expense sub-trees for this business as `open` directives in
|
|
41
|
+
`accounts.bean`, each with the KMU code it rolls up to, e.g.:
|
|
42
|
+
|
|
43
|
+
```beancount
|
|
44
|
+
2026-01-01 open Expenses:CH:GmbH:Marketing:Ads CHF
|
|
45
|
+
kmu: "6600" ; Advertising
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See the codes already in use:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
quints report konten --year 2026
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pick codes from the KMU Kontenrahmen; `quints check` fails on a `:CH:GmbH:`
|
|
55
|
+
account with no valid `kmu:` code.
|
|
56
|
+
|
|
57
|
+
## The loop — money out (statements → books)
|
|
58
|
+
|
|
59
|
+
1. Draft bank/PSP activity into `staging/`. Configured importers:
|
|
60
|
+
- `quints import ubs <statement.mt940>` — the MT940 export from UBS e-banking; no credentials.
|
|
61
|
+
- `quints import wise --fetch --from <date> --to <date>` — needs `QUINTS_WISE_API_TOKEN` in `.env` (plus `QUINTS_WISE_PRIVATE_KEY` for SCA-protected profiles; the key pair lives in `.wise/`, git-ignored).
|
|
62
|
+
- `quints import stripe --fetch --from <date> --to <date>` — needs `QUINTS_STRIPE_API_KEY` in `.env` (a restricted read-only key for the `[import.stripe]` account).
|
|
63
|
+
2. Review each draft in `staging/`. A draft is a flagged (`!`) transaction
|
|
64
|
+
with only the cash leg known:
|
|
65
|
+
|
|
66
|
+
```beancount
|
|
67
|
+
2026-07-20 ! "ACME AG" "Payment order"
|
|
68
|
+
Assets:CH:GmbH:Current:UBS:CHF -250.00 CHF
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Complete the counter leg, decide the VAT treatment (InputVAT /
|
|
72
|
+
Bezugsteuer / none), link the source document, flip `!` to `*`, and move
|
|
73
|
+
it into `books/2026.bean`. `quints match` scores staging drafts and
|
|
74
|
+
inbox documents against invoices and bookings.
|
|
75
|
+
3. **Always** `quints check` before you consider the books consistent.
|
|
76
|
+
|
|
77
|
+
## The loop — money in (invoice → receivable → payment)
|
|
78
|
+
|
|
79
|
+
1. Describe the invoice as a YAML file in `invoicing/` (each file carries a
|
|
80
|
+
`$schema` modeline, so schema-aware editors validate it as you type).
|
|
81
|
+
2. `quints invoice invoicing/<file>.yaml` renders the PDF into `documents/`
|
|
82
|
+
under the income account and cross-checks the total against the ledger.
|
|
83
|
+
Not booked yet? It prints the receivable draft to paste into
|
|
84
|
+
`books/2026.bean`.
|
|
85
|
+
3. The payment arrives with the next bank import; the draft is matched to
|
|
86
|
+
the open invoice by its QR/SCOR reference. `quints receivables` shows
|
|
87
|
+
what is still open.
|
|
88
|
+
|
|
89
|
+
## Machine-readable surfaces (prefer these over scraping text)
|
|
90
|
+
|
|
91
|
+
Every reporting command takes `--json` — stable keys, ISO dates, decimal
|
|
92
|
+
strings:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
quints check --json
|
|
96
|
+
quints mwst -q 2026-Q3 --json
|
|
97
|
+
quints status --json
|
|
98
|
+
quints report bilanz --at 2026-12-31 --json
|
|
99
|
+
quints receivables --json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
JSON Schemas for the invoicing files are hosted at
|
|
103
|
+
https://sealambda.github.io/quints/schema/ (`quints schema` writes them
|
|
104
|
+
locally to `invoicing/schema/`).
|
|
105
|
+
|
|
106
|
+
Never invent VAT numbers or rates — compute them with `quints mwst`.
|
|
107
|
+
|
|
108
|
+
## Sample data — replace before the books are real
|
|
109
|
+
|
|
110
|
+
The scaffold seeded a demo quarter so every command has data. Before
|
|
111
|
+
booking real activity:
|
|
112
|
+
|
|
113
|
+
- [ ] `invoicing/issuer.yaml` — the VAT ID (CHE-267.359.056 MWST) and both
|
|
114
|
+
IBANs are checksum-valid fakes; put the real ones in.
|
|
115
|
+
- [ ] `invoicing/customers.yaml` — replace the demo customers (acme, globex).
|
|
116
|
+
- [ ] `invoicing/acme-2026-07.yaml` and `invoicing/globex-2026-08.yaml`
|
|
117
|
+
— delete the demo invoices.
|
|
118
|
+
- [ ] `books/2026.bean` — delete the block marked *sample activity*.
|
|
119
|
+
- [ ] `prices.bean` — drop the demo EUR rates, then `quints prices sync`.
|
|
120
|
+
- [ ] `quints.toml` — the placeholder IBAN under `[import.ubs]`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sealambda.github.io/quints/schema/invoice.schema.json
|
|
2
|
+
# Sample domestic invoice — a Swiss QR-bill. It ties to the ^INV2026014
|
|
3
|
+
# booking in books/2026.bean (net 1'000.00 + 8.1% VAT = 1'081.00), so
|
|
4
|
+
# `quints invoice` cross-checks it clean against the ledger.
|
|
5
|
+
number: INV2026014
|
|
6
|
+
kind: domestic
|
|
7
|
+
currency: CHF
|
|
8
|
+
issue_date: 2026-07-02
|
|
9
|
+
supply: Juli 2026
|
|
10
|
+
customer: acme
|
|
11
|
+
items:
|
|
12
|
+
- description: Consulting — July
|
|
13
|
+
quantity: 1
|
|
14
|
+
unit_price: 1000.00
|
|
15
|
+
unit: Pauschal
|
|
16
|
+
locale: de_CH
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sealambda.github.io/quints/schema/customers.schema.json
|
|
2
|
+
# Customer registry — invoices reference these entries by key. A customer
|
|
3
|
+
# is a flat entry, or a dated `versions` history for address changes.
|
|
4
|
+
acme:
|
|
5
|
+
name: Acme AG
|
|
6
|
+
address:
|
|
7
|
+
- Bahnhofstrasse 1
|
|
8
|
+
- 8001 Zürich
|
|
9
|
+
globex:
|
|
10
|
+
name: Globex Ltd
|
|
11
|
+
country: IE
|
|
12
|
+
# Reverse-charge exports must carry the customer's VAT number.
|
|
13
|
+
vat_id: IE1234567T
|
|
14
|
+
address:
|
|
15
|
+
- 1 Liffey Street
|
|
16
|
+
- Dublin 1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sealambda.github.io/quints/schema/invoice.schema.json
|
|
2
|
+
# Sample export invoice — foreign currency, reverse charge, no QR part.
|
|
3
|
+
# Ties to the ^INV2026015 booking in books/2026.bean (500.00 EUR).
|
|
4
|
+
number: INV2026015
|
|
5
|
+
kind: export
|
|
6
|
+
currency: EUR
|
|
7
|
+
issue_date: 2026-08-05
|
|
8
|
+
supply: August 2026
|
|
9
|
+
customer: globex
|
|
10
|
+
items:
|
|
11
|
+
- description: Export consulting
|
|
12
|
+
quantity: 1
|
|
13
|
+
unit_price: 500.00
|
|
14
|
+
locale: en
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sealambda.github.io/quints/schema/issuer.schema.json
|
|
2
|
+
# Issuer identity for `quints invoice` — name, address, VAT ID, and one
|
|
3
|
+
# bank account per invoicing currency. Sample data: replace the VAT ID
|
|
4
|
+
# and IBANs with your own before issuing a real invoice.
|
|
5
|
+
name: Example GmbH
|
|
6
|
+
address:
|
|
7
|
+
- Beispielstrasse 1
|
|
8
|
+
- 8000 Zürich
|
|
9
|
+
vat_id: CHE-267.359.056 MWST
|
|
10
|
+
email: billing@example.ch
|
|
11
|
+
bank:
|
|
12
|
+
CHF:
|
|
13
|
+
# QR-IBAN (QR-IID variant) — a Swiss QR-bill with a QRR reference.
|
|
14
|
+
qr_iban: CH44 3199 9123 0008 8901 2
|
|
15
|
+
EUR:
|
|
16
|
+
# Regular IBAN — foreign transfers can't use the QR-bill scheme.
|
|
17
|
+
iban: CH93 0076 2011 6238 5295 7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quints"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Swiss VAT & accounting toolkit for plain-text (beancount) books: MWST reports and quarterly settlement, Bezugsteuer helpers, BAZG daily FX rates, QR-bill invoicing, KMU statutory statements, and statement importers (UBS MT940, Wise, Stripe) that draft into a staging area"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -15,7 +15,7 @@ dependencies = [
|
|
|
15
15
|
"beangulp-mt940>=0.1.0",
|
|
16
16
|
"beangulp-stripe>=0.1.0",
|
|
17
17
|
"beangulp-wise[sca]>=0.1.0",
|
|
18
|
-
"beanprice-bazg>=0.
|
|
18
|
+
"beanprice-bazg>=0.2.0",
|
|
19
19
|
"fava>=1.30",
|
|
20
20
|
"pydantic>=2.7",
|
|
21
21
|
"python-stdnum>=1.20",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""quints — Swiss VAT & accounting for plain-text (beancount) books."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
# Derived from the installed distribution (pyproject.toml is the single
|
|
6
|
+
# source) — a hardcoded string here drifted from the released version once.
|
|
7
|
+
__version__ = version("quints")
|
|
@@ -57,11 +57,26 @@ app = typer.Typer(
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def _print_version(value: bool) -> None:
|
|
61
|
+
if value:
|
|
62
|
+
from . import __version__
|
|
63
|
+
|
|
64
|
+
typer.echo(__version__)
|
|
65
|
+
raise typer.Exit()
|
|
66
|
+
|
|
67
|
+
|
|
60
68
|
@app.callback()
|
|
61
69
|
def _main(
|
|
62
70
|
config: Path | None = typer.Option(
|
|
63
71
|
None, "--config", help="quints.toml path (default: ./quints.toml, else built-in defaults)."
|
|
64
72
|
),
|
|
73
|
+
version: bool = typer.Option(
|
|
74
|
+
False,
|
|
75
|
+
"--version",
|
|
76
|
+
help="Print the quints version and exit.",
|
|
77
|
+
callback=_print_version,
|
|
78
|
+
is_eager=True,
|
|
79
|
+
),
|
|
65
80
|
):
|
|
66
81
|
config_mod.set_path(config)
|
|
67
82
|
|
|
@@ -330,8 +345,37 @@ def prices_sync(
|
|
|
330
345
|
Without --from: extend each currency forward to today (fast, daily use).
|
|
331
346
|
With --from DATE: re-scan the whole range and fill any missing days.
|
|
332
347
|
"""
|
|
348
|
+
from rich.progress import (
|
|
349
|
+
BarColumn,
|
|
350
|
+
MofNCompleteColumn,
|
|
351
|
+
Progress,
|
|
352
|
+
TaskID,
|
|
353
|
+
TextColumn,
|
|
354
|
+
TimeRemainingColumn,
|
|
355
|
+
)
|
|
356
|
+
|
|
333
357
|
repair = _parse_date(from_) if from_ else None
|
|
334
|
-
|
|
358
|
+
# BAZG has no bulk endpoint — the fetch is one request per calendar day per
|
|
359
|
+
# currency, so a backfill takes a while: show a live per-currency bar
|
|
360
|
+
# (stderr, transient) unless the caller asked for machine-readable output.
|
|
361
|
+
with Progress(
|
|
362
|
+
TextColumn("[progress.description]{task.description}"),
|
|
363
|
+
BarColumn(),
|
|
364
|
+
MofNCompleteColumn(),
|
|
365
|
+
TextColumn("days"),
|
|
366
|
+
TimeRemainingColumn(),
|
|
367
|
+
console=ui.err_console,
|
|
368
|
+
transient=True,
|
|
369
|
+
disable=as_json,
|
|
370
|
+
) as bars:
|
|
371
|
+
tasks: dict[str, TaskID] = {}
|
|
372
|
+
|
|
373
|
+
def on_progress(ccy: str, done: int, total: int) -> None:
|
|
374
|
+
if ccy not in tasks:
|
|
375
|
+
tasks[ccy] = bars.add_task(ccy, total=total)
|
|
376
|
+
bars.update(tasks[ccy], completed=done)
|
|
377
|
+
|
|
378
|
+
result = prices_mod.sync(out, repair_from=repair, progress=on_progress)
|
|
335
379
|
if as_json:
|
|
336
380
|
_json_out(
|
|
337
381
|
{
|
|
@@ -673,7 +717,10 @@ def check(
|
|
|
673
717
|
def invoice(
|
|
674
718
|
data: Path = typer.Argument(..., help="Invoice file (.yaml/.toml/.json)."),
|
|
675
719
|
out: Path | None = typer.Option(
|
|
676
|
-
None,
|
|
720
|
+
None,
|
|
721
|
+
"--out",
|
|
722
|
+
"-o",
|
|
723
|
+
help="Output PDF (default: filed under documents/<income account>/).",
|
|
677
724
|
),
|
|
678
725
|
issuer: Path = typer.Option(
|
|
679
726
|
Path("invoicing/issuer.yaml"), "--issuer", help="Issuer config (.yaml/.toml/.json)."
|
|
@@ -705,7 +752,12 @@ def invoice(
|
|
|
705
752
|
registry = m.load_customers(customers) if customers.exists() else None
|
|
706
753
|
inv = m.load_invoice(data, registry)
|
|
707
754
|
iss = m.load_issuer(issuer)
|
|
708
|
-
|
|
755
|
+
if out is None:
|
|
756
|
+
# File the PDF the way beancount documents are filed: under the income
|
|
757
|
+
# account's folder, date-prefixed, next to the ledger's other evidence.
|
|
758
|
+
cfg = config_mod.get()
|
|
759
|
+
account = cfg.income_export if inv.kind == "export" else cfg.income_domestic
|
|
760
|
+
out = m.document_path(inv, account)
|
|
709
761
|
path, totals, payload = r.render(inv, iss, out)
|
|
710
762
|
|
|
711
763
|
qr_ok = None
|
|
@@ -779,7 +831,9 @@ def schema(
|
|
|
779
831
|
"""Write JSON Schemas for the invoice, issuer, and customers files.
|
|
780
832
|
|
|
781
833
|
Point an editor at them (yaml-language-server modeline) for completion
|
|
782
|
-
and validation; any future UI can consume the same contract.
|
|
834
|
+
and validation; any future UI can consume the same contract. The same
|
|
835
|
+
schemas are published on the docs site, so scaffolded YAMLs reference
|
|
836
|
+
them without running this command."""
|
|
783
837
|
import json as _json
|
|
784
838
|
|
|
785
839
|
from .invoice import model as m
|
|
@@ -793,6 +847,7 @@ def schema(
|
|
|
793
847
|
path = out / f"{name}.schema.json"
|
|
794
848
|
path.write_text(_json.dumps(mdl.model_json_schema(), indent=2) + "\n")
|
|
795
849
|
ui.console.print(f"[ok]Wrote[/] {path}")
|
|
850
|
+
ui.console.print(f"[muted]Also hosted at {config_mod.DOCS_URL}/schema/[/]")
|
|
796
851
|
|
|
797
852
|
|
|
798
853
|
@app.command()
|
|
@@ -816,6 +871,11 @@ def init(
|
|
|
816
871
|
answers_file: Path | None = typer.Option(
|
|
817
872
|
None, "--answers", help="TOML answer-file for non-interactive scaffolding."
|
|
818
873
|
),
|
|
874
|
+
use_git: bool = typer.Option(
|
|
875
|
+
True,
|
|
876
|
+
"--git/--no-git",
|
|
877
|
+
help="git init + commit the pristine scaffold (skipped inside an existing repo).",
|
|
878
|
+
),
|
|
819
879
|
force: bool = typer.Option(False, "--force", help="Overwrite existing files."),
|
|
820
880
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip prompts; accept defaults."),
|
|
821
881
|
as_json: bool = typer.Option(False, "--json", help="Machine-readable output."),
|
|
@@ -879,14 +939,18 @@ def init(
|
|
|
879
939
|
typer.secho(f"ERROR: {e}", fg="red", err=True)
|
|
880
940
|
raise typer.Exit(1) from None
|
|
881
941
|
result = init_mod.write(directory, files, force=force)
|
|
942
|
+
git_result = init_mod.init_git(directory) if use_git and result.written else None
|
|
882
943
|
|
|
883
944
|
if as_json:
|
|
945
|
+
import dataclasses
|
|
946
|
+
|
|
884
947
|
_json_out(
|
|
885
948
|
{
|
|
886
949
|
"directory": str(directory),
|
|
887
950
|
"entity": answers.entity_name,
|
|
888
951
|
"written": [str(p) for p in result.written],
|
|
889
952
|
"skipped": [str(p) for p in result.skipped],
|
|
953
|
+
"git": dataclasses.asdict(git_result) if git_result else None,
|
|
890
954
|
}
|
|
891
955
|
)
|
|
892
956
|
return
|
|
@@ -894,6 +958,11 @@ def init(
|
|
|
894
958
|
ui.console.print(f"[ok]created[/] {path}")
|
|
895
959
|
for path in result.skipped:
|
|
896
960
|
ui.console.print(f"[warn]exists, skipped[/] {path} (use --force to overwrite)")
|
|
961
|
+
if git_result:
|
|
962
|
+
if git_result.committed:
|
|
963
|
+
ui.console.print("[ok]git[/] initialised repository, committed the scaffold")
|
|
964
|
+
else:
|
|
965
|
+
ui.console.print(f"[warn]git[/] {git_result.detail}")
|
|
897
966
|
if result.written and not result.skipped:
|
|
898
967
|
ui.console.print(
|
|
899
968
|
f"\nScaffolded [b]{answers.entity_name}[/] in {directory}. "
|
|
@@ -28,6 +28,11 @@ else: # pragma: no cover
|
|
|
28
28
|
|
|
29
29
|
DEFAULT_PATH = Path("quints.toml")
|
|
30
30
|
|
|
31
|
+
# The published documentation site. The invoice/issuer/customers JSON Schemas
|
|
32
|
+
# are hosted under <DOCS_URL>/schema/ (regenerated by `make docs`, sync-tested
|
|
33
|
+
# in CI); `quints init` writes yaml-language-server modelines pointing there.
|
|
34
|
+
DOCS_URL = "https://sealambda.github.io/quints"
|
|
35
|
+
|
|
31
36
|
# Legal form → account-name component (Assets:CH:<component>:…). The keys are
|
|
32
37
|
# the entity families the official KMU Kontenrahmen (veb.ch) prints Klasse-28
|
|
33
38
|
# variants for: juristische Personen (gmbh, ag) and Einzelunternehmen;
|