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.
Files changed (79) hide show
  1. {quints-0.2.0 → quints-0.4.0}/.gitignore +1 -0
  2. {quints-0.2.0 → quints-0.4.0}/PKG-INFO +5 -3
  3. {quints-0.2.0 → quints-0.4.0}/README.md +3 -1
  4. quints-0.4.0/examples/.gitignore +8 -0
  5. quints-0.4.0/examples/AGENTS.md +120 -0
  6. quints-0.4.0/examples/CLAUDE.md +1 -0
  7. quints-0.4.0/examples/invoicing/acme-2026-07.yaml +16 -0
  8. quints-0.4.0/examples/invoicing/customers.yaml +16 -0
  9. quints-0.4.0/examples/invoicing/globex-2026-08.yaml +14 -0
  10. quints-0.4.0/examples/invoicing/issuer.yaml +17 -0
  11. {quints-0.2.0 → quints-0.4.0}/pyproject.toml +2 -2
  12. quints-0.4.0/src/quints/__init__.py +7 -0
  13. {quints-0.2.0 → quints-0.4.0}/src/quints/cli.py +73 -4
  14. {quints-0.2.0 → quints-0.4.0}/src/quints/config.py +5 -0
  15. {quints-0.2.0 → quints-0.4.0}/src/quints/init.py +299 -17
  16. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/model.py +15 -0
  17. {quints-0.2.0 → quints-0.4.0}/src/quints/prices.py +50 -3
  18. quints-0.4.0/tests/test_docs.py +137 -0
  19. {quints-0.2.0 → quints-0.4.0}/tests/test_init.py +182 -0
  20. {quints-0.2.0 → quints-0.4.0}/tests/test_prices_sync.py +21 -1
  21. quints-0.2.0/examples/.gitignore +0 -8
  22. quints-0.2.0/examples/AGENTS.md +0 -56
  23. quints-0.2.0/examples/invoicing/acme-2026-07.yaml +0 -19
  24. quints-0.2.0/examples/invoicing/issuer.yaml +0 -12
  25. quints-0.2.0/src/quints/__init__.py +0 -3
  26. quints-0.2.0/tests/test_docs.py +0 -48
  27. {quints-0.2.0 → quints-0.4.0}/LICENSE +0 -0
  28. {quints-0.2.0 → quints-0.4.0}/examples/accounts.bean +0 -0
  29. {quints-0.2.0 → quints-0.4.0}/examples/answers.toml +0 -0
  30. {quints-0.2.0 → quints-0.4.0}/examples/books/2026.bean +0 -0
  31. {quints-0.2.0 → quints-0.4.0}/examples/commodities.bean +0 -0
  32. {quints-0.2.0 → quints-0.4.0}/examples/documents/.gitkeep +0 -0
  33. {quints-0.2.0 → quints-0.4.0}/examples/inbox/.gitkeep +0 -0
  34. {quints-0.2.0 → quints-0.4.0}/examples/main.bean +0 -0
  35. {quints-0.2.0 → quints-0.4.0}/examples/prices.bean +0 -0
  36. {quints-0.2.0 → quints-0.4.0}/examples/pyproject.toml +0 -0
  37. {quints-0.2.0 → quints-0.4.0}/examples/quints.toml +0 -0
  38. {quints-0.2.0 → quints-0.4.0}/examples/staging/.gitkeep +0 -0
  39. {quints-0.2.0 → quints-0.4.0}/examples/statements/ubs-2026.mt940 +0 -0
  40. {quints-0.2.0 → quints-0.4.0}/src/quints/fava/__init__.py +0 -0
  41. {quints-0.2.0 → quints-0.4.0}/src/quints/fava/templates/QuintDashboard.html +0 -0
  42. {quints-0.2.0 → quints-0.4.0}/src/quints/fx.py +0 -0
  43. {quints-0.2.0 → quints-0.4.0}/src/quints/importing.py +0 -0
  44. {quints-0.2.0 → quints-0.4.0}/src/quints/inbox.py +0 -0
  45. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/__init__.py +0 -0
  46. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/draft.py +0 -0
  47. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/labels.py +0 -0
  48. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/qr.py +0 -0
  49. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/render.py +0 -0
  50. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/template.typ +0 -0
  51. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/vatid.py +0 -0
  52. {quints-0.2.0 → quints-0.4.0}/src/quints/invoice/verify.py +0 -0
  53. {quints-0.2.0 → quints-0.4.0}/src/quints/kmu.py +0 -0
  54. {quints-0.2.0 → quints-0.4.0}/src/quints/ledger.py +0 -0
  55. {quints-0.2.0 → quints-0.4.0}/src/quints/match.py +0 -0
  56. {quints-0.2.0 → quints-0.4.0}/src/quints/mwst.py +0 -0
  57. {quints-0.2.0 → quints-0.4.0}/src/quints/plugins/__init__.py +0 -0
  58. {quints-0.2.0 → quints-0.4.0}/src/quints/plugins/kmu.py +0 -0
  59. {quints-0.2.0 → quints-0.4.0}/src/quints/receivables.py +0 -0
  60. {quints-0.2.0 → quints-0.4.0}/src/quints/report.typ +0 -0
  61. {quints-0.2.0 → quints-0.4.0}/src/quints/report_pdf.py +0 -0
  62. {quints-0.2.0 → quints-0.4.0}/src/quints/settlement.py +0 -0
  63. {quints-0.2.0 → quints-0.4.0}/src/quints/ui.py +0 -0
  64. {quints-0.2.0 → quints-0.4.0}/src/quints/vat.py +0 -0
  65. {quints-0.2.0 → quints-0.4.0}/tests/fixtures/transactions.mt940 +0 -0
  66. {quints-0.2.0 → quints-0.4.0}/tests/test_cli_json.py +0 -0
  67. {quints-0.2.0 → quints-0.4.0}/tests/test_config.py +0 -0
  68. {quints-0.2.0 → quints-0.4.0}/tests/test_fava_ext.py +0 -0
  69. {quints-0.2.0 → quints-0.4.0}/tests/test_fx.py +0 -0
  70. {quints-0.2.0 → quints-0.4.0}/tests/test_importing.py +0 -0
  71. {quints-0.2.0 → quints-0.4.0}/tests/test_inbox_match.py +0 -0
  72. {quints-0.2.0 → quints-0.4.0}/tests/test_invoice.py +0 -0
  73. {quints-0.2.0 → quints-0.4.0}/tests/test_kmu_plugin.py +0 -0
  74. {quints-0.2.0 → quints-0.4.0}/tests/test_kmu_report.py +0 -0
  75. {quints-0.2.0 → quints-0.4.0}/tests/test_ledger.py +0 -0
  76. {quints-0.2.0 → quints-0.4.0}/tests/test_mwst.py +0 -0
  77. {quints-0.2.0 → quints-0.4.0}/tests/test_receivables.py +0 -0
  78. {quints-0.2.0 → quints-0.4.0}/tests/test_report_pdf.py +0 -0
  79. {quints-0.2.0 → quints-0.4.0}/tests/test_settlement.py +0 -0
@@ -6,6 +6,7 @@ build/
6
6
  *.egg-info/
7
7
  .pytest_cache/
8
8
  .ruff_cache/
9
+ site/
9
10
  .env
10
11
  .DS_Store
11
12
  .claude/settings.local.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quints
3
- Version: 0.2.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.1.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
- Everything a Swiss micro-company (GmbH/AG) needs on top of beancount + Fava:
30
+ ![quints init scaffolds sample books, quints check validates them, and quints mwst prints the Form-310 VAT return](https://raw.githubusercontent.com/sealambda/quints/main/docs/assets/quickstart.gif)
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
- Everything a Swiss micro-company (GmbH/AG) needs on top of beancount + Fava:
5
+ ![quints init scaffolds sample books, quints check validates them, and quints mwst prints the Form-310 VAT return](https://raw.githubusercontent.com/sealambda/quints/main/docs/assets/quickstart.gif)
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,8 @@
1
+ # quints working directories. documents/ (filed sources and rendered
2
+ # invoices) is deliberately NOT ignored — the ledger links to it.
3
+ /staging/
4
+ .env
5
+ __pycache__/
6
+ .venv/
7
+ .DS_Store
8
+ .wise/
@@ -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.2.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.1.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
- result = prices_mod.sync(out, repair_from=repair)
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, "--out", "-o", help="Output PDF (default: <number>.pdf)."
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
- out = out or Path(f"{inv.number}.pdf")
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;