doppia 0.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.
- doppia-0.1.0/LICENSE +21 -0
- doppia-0.1.0/PKG-INFO +151 -0
- doppia-0.1.0/README.md +127 -0
- doppia-0.1.0/pyproject.toml +41 -0
- doppia-0.1.0/setup.cfg +4 -0
- doppia-0.1.0/src/doppia/__init__.py +0 -0
- doppia-0.1.0/src/doppia/export.py +79 -0
- doppia-0.1.0/src/doppia/gui/__init__.py +568 -0
- doppia-0.1.0/src/doppia/gui/dialogs.py +1221 -0
- doppia-0.1.0/src/doppia/gui/models.py +274 -0
- doppia-0.1.0/src/doppia/importer.py +223 -0
- doppia-0.1.0/src/doppia/ledger.py +352 -0
- doppia-0.1.0/src/doppia/llm.py +255 -0
- doppia-0.1.0/src/doppia.egg-info/PKG-INFO +151 -0
- doppia-0.1.0/src/doppia.egg-info/SOURCES.txt +18 -0
- doppia-0.1.0/src/doppia.egg-info/dependency_links.txt +1 -0
- doppia-0.1.0/src/doppia.egg-info/entry_points.txt +2 -0
- doppia-0.1.0/src/doppia.egg-info/requires.txt +4 -0
- doppia-0.1.0/src/doppia.egg-info/top_level.txt +1 -0
- doppia-0.1.0/test/test_ledger.py +250 -0
doppia-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Dave Vandenbout.
|
|
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.
|
doppia-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doppia
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Personal accounting for bank and credit card accounts, stored in CountAbout CSV format
|
|
5
|
+
Author: Dave Vandenbout
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/devbisme/doppia
|
|
8
|
+
Project-URL: Source, https://github.com/devbisme/doppia
|
|
9
|
+
Project-URL: Issues, https://github.com/devbisme/doppia/issues
|
|
10
|
+
Project-URL: Releases, https://github.com/devbisme/doppia/releases
|
|
11
|
+
Keywords: accounting,personal finance,ledger,countabout,bookkeeping
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: PySide6>=6.6
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest; extra == "test"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# doppia
|
|
26
|
+
|
|
27
|
+
Personal accounting for bank and credit card accounts (PySide6 desktop GUI).
|
|
28
|
+
Data lives in a single CSV in CountAbout export format; files are written back
|
|
29
|
+
byte-for-byte compatible (unchanged rows are untouched, quoting matches CountAbout).
|
|
30
|
+
|
|
31
|
+
## The name
|
|
32
|
+
|
|
33
|
+
doppia is named after *partita doppia*, Italian for double-entry bookkeeping. The method was
|
|
34
|
+
first described in print by Luca Pacioli (c. 1447–1517), an Italian mathematician and
|
|
35
|
+
Franciscan friar, in his *Summa de arithmetica, geometria, proportioni et proportionalita*
|
|
36
|
+
(Venice, 1494), which earned him the title "father of accounting".
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
Install doppia from PyPI as a standalone app with
|
|
41
|
+
[pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/), which keep it isolated
|
|
42
|
+
without you having to manage a virtual environment:
|
|
43
|
+
|
|
44
|
+
pipx install doppia # or: uv tool install doppia
|
|
45
|
+
|
|
46
|
+
Python 3.10 or later is required. The clipboard import and **Guess with LLM** features also
|
|
47
|
+
need [Ollama](https://ollama.com) with a model installed, e.g. `ollama pull gemma4:12b-it-qat`;
|
|
48
|
+
everything else works without it.
|
|
49
|
+
|
|
50
|
+
## Run
|
|
51
|
+
|
|
52
|
+
doppia path/to/countabout-export.csv # reopens the last file if omitted
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
From a clone of the repository, [uv](https://docs.astral.sh/uv/) runs the app and tests
|
|
57
|
+
with the dependencies it needs:
|
|
58
|
+
|
|
59
|
+
uv run doppia path/to/countabout-export.csv
|
|
60
|
+
uv run --extra test pytest
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- Account list with today's balance and cleared balance. The bold **Total** row sums both
|
|
65
|
+
columns over the selected accounts (all accounts when none or "All accounts" is selected).
|
|
66
|
+
Future-dated rows shown grey.
|
|
67
|
+
- Money is shown as `$1,234.56`: red for negative, green for positive, no minus sign
|
|
68
|
+
(the minus is kept only in plain-text messages that can't be colored). Colors stay
|
|
69
|
+
visible on selected rows. `$` and commas are accepted when typing amounts.
|
|
70
|
+
- Register per account or all accounts: running balance, uncleared-only filter, and a
|
|
71
|
+
filter field that takes a case-insensitive regular expression (e.g. `^amazon`, `3000`,
|
|
72
|
+
`\$3,000`) matched in the description, category, memo, ref, date, or amount. Its X button
|
|
73
|
+
clears it; an invalid expression tints the field and is ignored. The account names over
|
|
74
|
+
the register are cut to 30 characters, including `...` (hover for the full list).
|
|
75
|
+
- Ctrl/Shift-click in the account list to select several accounts; the register shows their
|
|
76
|
+
combined transactions with a combined running balance. Reconcile and Rename account need
|
|
77
|
+
a single account selected.
|
|
78
|
+
- Click a column header to sort by it; click again to reverse. The sort column's header
|
|
79
|
+
is highlighted, and the selection is kept when re-sorting. The register opens sorted by
|
|
80
|
+
date, newest first. The Balance column is a date-order running balance, so it only
|
|
81
|
+
adds up when sorted by date.
|
|
82
|
+
- Click the Clr box to toggle cleared; cleared rows show a green ✔ (the CSV still stores `X`).
|
|
83
|
+
- Double-click a row to edit it; right-click for a menu to delete it.
|
|
84
|
+
- New/edit/delete (Ctrl+N / Ctrl+E / Ctrl+Del), Ctrl+K toggles cleared.
|
|
85
|
+
Payee memory: typing a known description fills its last category and amount.
|
|
86
|
+
- Search and replace (Transaction menu, Ctrl+H): find a regular expression in the
|
|
87
|
+
Description, Category, Memo, Ref, and/or Tags fields (optionally case-sensitive, optionally
|
|
88
|
+
only in the selected accounts). Check **Replace with** to preview each change (`\1` inserts a
|
|
89
|
+
matched group), untick any you don't want (**Select All/None** flips them all), then
|
|
90
|
+
**Replace checked**. Transfer/split
|
|
91
|
+
categories and results containing line breaks can't be replaced. Nothing is written to the
|
|
92
|
+
file until you save.
|
|
93
|
+
- Transfers use CountAbout's `{Account}` category; the mirror row in the other account
|
|
94
|
+
is created/updated/deleted automatically (matched by date and negated amount).
|
|
95
|
+
- Bank CSV import (Ctrl+I): column mapping saved as named profiles in
|
|
96
|
+
`~/.config/doppia/import_profiles.json`; payee/category matched from history;
|
|
97
|
+
duplicates (same amount within 3 days) unchecked by default; imported rows marked cleared.
|
|
98
|
+
- **Guess with LLM** asks the local model (the one chosen in clipboard import) which columns
|
|
99
|
+
hold the date, description, amount or debit/credit, memo, and check number, and the date
|
|
100
|
+
format. Only the header and first 8 rows are sent. Its answer is limited to the file's
|
|
101
|
+
real column names and checked against the sample rows; **Flip signs** is set from the
|
|
102
|
+
data (mostly positive amounts = card charges listed as positive), not by the model.
|
|
103
|
+
Takes a few seconds; save the result as a profile to skip it next time.
|
|
104
|
+
- Import from clipboard (File menu, Ctrl+Shift+V): copy transactions from a bank or card
|
|
105
|
+
website or statement, and a local LLM served by [Ollama](https://ollama.com) turns the text
|
|
106
|
+
into transactions for the chosen account (the active one by default). Nothing leaves your
|
|
107
|
+
machine. The text is shown for editing; press **Parse** (about 15–30 s on an RTX 3060).
|
|
108
|
+
- Model: any installed Ollama model, remembered between runs. Default `gemma4:12b-it-qat`,
|
|
109
|
+
which read sample bank and card text correctly; smaller models got dates and signs wrong.
|
|
110
|
+
Set `OLLAMA_HOST` if Ollama isn't at `localhost:11434`.
|
|
111
|
+
- Likely duplicates of existing transactions (same amount within 3 days) are removed from the
|
|
112
|
+
list; **Show likely duplicates** brings them back, unchecked. Payees and categories are
|
|
113
|
+
matched from history, as in CSV import.
|
|
114
|
+
- Review before importing: amounts the model returns that don't appear in the pasted text
|
|
115
|
+
are unchecked and flagged, and **Flip signs** fixes deposits/charges read backwards.
|
|
116
|
+
- Imported rows are marked cleared (optional) except ones shown as pending.
|
|
117
|
+
- Reconcile (Ctrl+R) against a statement ending balance: check off uncleared
|
|
118
|
+
transactions until the difference is $0.00.
|
|
119
|
+
- Reports (Ctrl+P): totals by category, category by month, income vs. expense by month.
|
|
120
|
+
Click a column header to sort.
|
|
121
|
+
- Transfers are excluded unless **Include transfers** is checked. Then each transfer is
|
|
122
|
+
reported under `Transfer: <other account>`. A transfer between two checked accounts appears
|
|
123
|
+
on both sides, so it adds to both income and expense while netting to zero.
|
|
124
|
+
- **Accounts All/None** checks or unchecks every account in the account list.
|
|
125
|
+
- **Categories** field: a case-insensitive regular expression matched anywhere in the
|
|
126
|
+
category name (e.g. `^(groceries|dining)$`). Press Enter or Run to apply; the X button
|
|
127
|
+
clears it and reruns. An invalid expression is reported and ignored.
|
|
128
|
+
- Ctrl/Shift-click category rows, then Run, to report only those categories. The
|
|
129
|
+
restriction persists across runs until **Show all categories** clears it (along with
|
|
130
|
+
the regex). Run uses whatever rows are highlighted when pressed.
|
|
131
|
+
- Total income, expense, and net rows reflect only the categories shown.
|
|
132
|
+
- **Save…** writes the report as shown (current sort order) to CSV (plain signed numbers for
|
|
133
|
+
spreadsheets), Markdown, plain text (aligned columns), or PDF. Markdown, text, and PDF include the report settings (dates,
|
|
134
|
+
accounts, category filters) and show amounts with a minus sign, since they may be printed
|
|
135
|
+
without color. Wide month-by-category reports print landscape in a smaller font.
|
|
136
|
+
- View → Font… sets the font family, style (regular/italic/bold), and size for the whole
|
|
137
|
+
app; the choice is remembered. View → Reset font returns to the system default.
|
|
138
|
+
- Save (Ctrl+S) writes atomically and keeps the previous file as `<file>.bak`.
|
|
139
|
+
- Handles a 10k+ row ledger quickly: about 0.6 s to start and 0.1 s to switch accounts.
|
|
140
|
+
|
|
141
|
+
## Limitations (from the CountAbout format)
|
|
142
|
+
|
|
143
|
+
- Split transactions: existing `{SPLIT}` rows are preserved, but the export carries no
|
|
144
|
+
split detail, so new splits cannot be created and reports show splits as one lump.
|
|
145
|
+
- Balances are sums of rows; there is no separate opening-balance field. New accounts
|
|
146
|
+
get a "Beginning Balance" row.
|
|
147
|
+
- `Cleared` is X/blank only (no separate reconciled state).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT; see [LICENSE](https://github.com/devbisme/doppia/blob/master/LICENSE).
|
doppia-0.1.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# doppia
|
|
2
|
+
|
|
3
|
+
Personal accounting for bank and credit card accounts (PySide6 desktop GUI).
|
|
4
|
+
Data lives in a single CSV in CountAbout export format; files are written back
|
|
5
|
+
byte-for-byte compatible (unchanged rows are untouched, quoting matches CountAbout).
|
|
6
|
+
|
|
7
|
+
## The name
|
|
8
|
+
|
|
9
|
+
doppia is named after *partita doppia*, Italian for double-entry bookkeeping. The method was
|
|
10
|
+
first described in print by Luca Pacioli (c. 1447–1517), an Italian mathematician and
|
|
11
|
+
Franciscan friar, in his *Summa de arithmetica, geometria, proportioni et proportionalita*
|
|
12
|
+
(Venice, 1494), which earned him the title "father of accounting".
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Install doppia from PyPI as a standalone app with
|
|
17
|
+
[pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/), which keep it isolated
|
|
18
|
+
without you having to manage a virtual environment:
|
|
19
|
+
|
|
20
|
+
pipx install doppia # or: uv tool install doppia
|
|
21
|
+
|
|
22
|
+
Python 3.10 or later is required. The clipboard import and **Guess with LLM** features also
|
|
23
|
+
need [Ollama](https://ollama.com) with a model installed, e.g. `ollama pull gemma4:12b-it-qat`;
|
|
24
|
+
everything else works without it.
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
doppia path/to/countabout-export.csv # reopens the last file if omitted
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
From a clone of the repository, [uv](https://docs.astral.sh/uv/) runs the app and tests
|
|
33
|
+
with the dependencies it needs:
|
|
34
|
+
|
|
35
|
+
uv run doppia path/to/countabout-export.csv
|
|
36
|
+
uv run --extra test pytest
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Account list with today's balance and cleared balance. The bold **Total** row sums both
|
|
41
|
+
columns over the selected accounts (all accounts when none or "All accounts" is selected).
|
|
42
|
+
Future-dated rows shown grey.
|
|
43
|
+
- Money is shown as `$1,234.56`: red for negative, green for positive, no minus sign
|
|
44
|
+
(the minus is kept only in plain-text messages that can't be colored). Colors stay
|
|
45
|
+
visible on selected rows. `$` and commas are accepted when typing amounts.
|
|
46
|
+
- Register per account or all accounts: running balance, uncleared-only filter, and a
|
|
47
|
+
filter field that takes a case-insensitive regular expression (e.g. `^amazon`, `3000`,
|
|
48
|
+
`\$3,000`) matched in the description, category, memo, ref, date, or amount. Its X button
|
|
49
|
+
clears it; an invalid expression tints the field and is ignored. The account names over
|
|
50
|
+
the register are cut to 30 characters, including `...` (hover for the full list).
|
|
51
|
+
- Ctrl/Shift-click in the account list to select several accounts; the register shows their
|
|
52
|
+
combined transactions with a combined running balance. Reconcile and Rename account need
|
|
53
|
+
a single account selected.
|
|
54
|
+
- Click a column header to sort by it; click again to reverse. The sort column's header
|
|
55
|
+
is highlighted, and the selection is kept when re-sorting. The register opens sorted by
|
|
56
|
+
date, newest first. The Balance column is a date-order running balance, so it only
|
|
57
|
+
adds up when sorted by date.
|
|
58
|
+
- Click the Clr box to toggle cleared; cleared rows show a green ✔ (the CSV still stores `X`).
|
|
59
|
+
- Double-click a row to edit it; right-click for a menu to delete it.
|
|
60
|
+
- New/edit/delete (Ctrl+N / Ctrl+E / Ctrl+Del), Ctrl+K toggles cleared.
|
|
61
|
+
Payee memory: typing a known description fills its last category and amount.
|
|
62
|
+
- Search and replace (Transaction menu, Ctrl+H): find a regular expression in the
|
|
63
|
+
Description, Category, Memo, Ref, and/or Tags fields (optionally case-sensitive, optionally
|
|
64
|
+
only in the selected accounts). Check **Replace with** to preview each change (`\1` inserts a
|
|
65
|
+
matched group), untick any you don't want (**Select All/None** flips them all), then
|
|
66
|
+
**Replace checked**. Transfer/split
|
|
67
|
+
categories and results containing line breaks can't be replaced. Nothing is written to the
|
|
68
|
+
file until you save.
|
|
69
|
+
- Transfers use CountAbout's `{Account}` category; the mirror row in the other account
|
|
70
|
+
is created/updated/deleted automatically (matched by date and negated amount).
|
|
71
|
+
- Bank CSV import (Ctrl+I): column mapping saved as named profiles in
|
|
72
|
+
`~/.config/doppia/import_profiles.json`; payee/category matched from history;
|
|
73
|
+
duplicates (same amount within 3 days) unchecked by default; imported rows marked cleared.
|
|
74
|
+
- **Guess with LLM** asks the local model (the one chosen in clipboard import) which columns
|
|
75
|
+
hold the date, description, amount or debit/credit, memo, and check number, and the date
|
|
76
|
+
format. Only the header and first 8 rows are sent. Its answer is limited to the file's
|
|
77
|
+
real column names and checked against the sample rows; **Flip signs** is set from the
|
|
78
|
+
data (mostly positive amounts = card charges listed as positive), not by the model.
|
|
79
|
+
Takes a few seconds; save the result as a profile to skip it next time.
|
|
80
|
+
- Import from clipboard (File menu, Ctrl+Shift+V): copy transactions from a bank or card
|
|
81
|
+
website or statement, and a local LLM served by [Ollama](https://ollama.com) turns the text
|
|
82
|
+
into transactions for the chosen account (the active one by default). Nothing leaves your
|
|
83
|
+
machine. The text is shown for editing; press **Parse** (about 15–30 s on an RTX 3060).
|
|
84
|
+
- Model: any installed Ollama model, remembered between runs. Default `gemma4:12b-it-qat`,
|
|
85
|
+
which read sample bank and card text correctly; smaller models got dates and signs wrong.
|
|
86
|
+
Set `OLLAMA_HOST` if Ollama isn't at `localhost:11434`.
|
|
87
|
+
- Likely duplicates of existing transactions (same amount within 3 days) are removed from the
|
|
88
|
+
list; **Show likely duplicates** brings them back, unchecked. Payees and categories are
|
|
89
|
+
matched from history, as in CSV import.
|
|
90
|
+
- Review before importing: amounts the model returns that don't appear in the pasted text
|
|
91
|
+
are unchecked and flagged, and **Flip signs** fixes deposits/charges read backwards.
|
|
92
|
+
- Imported rows are marked cleared (optional) except ones shown as pending.
|
|
93
|
+
- Reconcile (Ctrl+R) against a statement ending balance: check off uncleared
|
|
94
|
+
transactions until the difference is $0.00.
|
|
95
|
+
- Reports (Ctrl+P): totals by category, category by month, income vs. expense by month.
|
|
96
|
+
Click a column header to sort.
|
|
97
|
+
- Transfers are excluded unless **Include transfers** is checked. Then each transfer is
|
|
98
|
+
reported under `Transfer: <other account>`. A transfer between two checked accounts appears
|
|
99
|
+
on both sides, so it adds to both income and expense while netting to zero.
|
|
100
|
+
- **Accounts All/None** checks or unchecks every account in the account list.
|
|
101
|
+
- **Categories** field: a case-insensitive regular expression matched anywhere in the
|
|
102
|
+
category name (e.g. `^(groceries|dining)$`). Press Enter or Run to apply; the X button
|
|
103
|
+
clears it and reruns. An invalid expression is reported and ignored.
|
|
104
|
+
- Ctrl/Shift-click category rows, then Run, to report only those categories. The
|
|
105
|
+
restriction persists across runs until **Show all categories** clears it (along with
|
|
106
|
+
the regex). Run uses whatever rows are highlighted when pressed.
|
|
107
|
+
- Total income, expense, and net rows reflect only the categories shown.
|
|
108
|
+
- **Save…** writes the report as shown (current sort order) to CSV (plain signed numbers for
|
|
109
|
+
spreadsheets), Markdown, plain text (aligned columns), or PDF. Markdown, text, and PDF include the report settings (dates,
|
|
110
|
+
accounts, category filters) and show amounts with a minus sign, since they may be printed
|
|
111
|
+
without color. Wide month-by-category reports print landscape in a smaller font.
|
|
112
|
+
- View → Font… sets the font family, style (regular/italic/bold), and size for the whole
|
|
113
|
+
app; the choice is remembered. View → Reset font returns to the system default.
|
|
114
|
+
- Save (Ctrl+S) writes atomically and keeps the previous file as `<file>.bak`.
|
|
115
|
+
- Handles a 10k+ row ledger quickly: about 0.6 s to start and 0.1 s to switch accounts.
|
|
116
|
+
|
|
117
|
+
## Limitations (from the CountAbout format)
|
|
118
|
+
|
|
119
|
+
- Split transactions: existing `{SPLIT}` rows are preserved, but the export carries no
|
|
120
|
+
split detail, so new splits cannot be created and reports show splits as one lump.
|
|
121
|
+
- Balances are sums of rows; there is no separate opening-balance field. New accounts
|
|
122
|
+
get a "Beginning Balance" row.
|
|
123
|
+
- `Cleared` is X/blank only (no separate reconciled state).
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT; see [LICENSE](https://github.com/devbisme/doppia/blob/master/LICENSE).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"] # for SPDX license metadata
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "doppia"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Personal accounting for bank and credit card accounts, stored in CountAbout CSV format"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Dave Vandenbout" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
keywords = ["accounting", "personal finance", "ledger", "countabout", "bookkeeping"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: X11 Applications :: Qt",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["PySide6>=6.6"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/devbisme/doppia"
|
|
26
|
+
Source = "https://github.com/devbisme/doppia"
|
|
27
|
+
Issues = "https://github.com/devbisme/doppia/issues"
|
|
28
|
+
Releases = "https://github.com/devbisme/doppia/releases"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
test = ["pytest"]
|
|
32
|
+
|
|
33
|
+
[project.gui-scripts]
|
|
34
|
+
doppia = "doppia.gui:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["test"]
|
|
41
|
+
pythonpath = ["src"]
|
doppia-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Render a report (title, notes, headers, rows) as CSV, Markdown, plain text, or HTML (for PDF).
|
|
2
|
+
|
|
3
|
+
Row values are str, int, or Decimal; Decimals are monetary amounts.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import html
|
|
8
|
+
import io
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
|
|
11
|
+
NEG_HTML = "#be1e1e"
|
|
12
|
+
POS_HTML = "#149628"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _money(value):
|
|
16
|
+
"""Signed $ amount: exported files may be printed or viewed without color."""
|
|
17
|
+
return f"{'-' if value < 0 else ''}${abs(value):,.2f}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_csv(headers, rows):
|
|
21
|
+
"""Plain numbers (e.g. -709.30) so spreadsheets can total them."""
|
|
22
|
+
out = io.StringIO()
|
|
23
|
+
w = csv.writer(out, lineterminator="\n")
|
|
24
|
+
w.writerow(headers)
|
|
25
|
+
for row in rows:
|
|
26
|
+
w.writerow([f"{v:.2f}" if isinstance(v, Decimal) else v for v in row])
|
|
27
|
+
return out.getvalue()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _md_cell(text):
|
|
31
|
+
return str(text).replace("|", "\\|")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_markdown(title, notes, headers, rows):
|
|
35
|
+
money_cols = {c for row in rows for c, v in enumerate(row) if isinstance(v, Decimal)}
|
|
36
|
+
lines = [f"# {title}", ""]
|
|
37
|
+
lines += [f"- {n}" for n in notes]
|
|
38
|
+
lines += ["", "| " + " | ".join(_md_cell(h) for h in headers) + " |"]
|
|
39
|
+
lines.append("|" + "|".join("---:" if c in money_cols else "---" for c in range(len(headers))) + "|")
|
|
40
|
+
for row in rows:
|
|
41
|
+
lines.append("| " + " | ".join(_md_cell(_money(v) if isinstance(v, Decimal) else v) for v in row) + " |")
|
|
42
|
+
return "\n".join(lines) + "\n"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_text(title, notes, headers, rows):
|
|
46
|
+
"""Fixed-width columns: amounts and counts right-aligned, other text left-aligned."""
|
|
47
|
+
cells = [[_money(v) if isinstance(v, Decimal) else str(v) for v in row] for row in rows]
|
|
48
|
+
right = {c for row in rows for c, v in enumerate(row) if isinstance(v, (Decimal, int))}
|
|
49
|
+
widths = [max([len(h)] + [len(row[c]) for row in cells]) for c, h in enumerate(headers)]
|
|
50
|
+
|
|
51
|
+
def line(values):
|
|
52
|
+
return " ".join(v.rjust(w) if c in right else v.ljust(w) for c, (v, w) in enumerate(zip(values, widths))).rstrip()
|
|
53
|
+
|
|
54
|
+
lines = [title, "=" * len(title), ""] + notes + ["", line(headers), line(["-" * w for w in widths])]
|
|
55
|
+
lines += [line(row) for row in cells]
|
|
56
|
+
return "\n".join(lines) + "\n"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def to_html(title, notes, headers, rows, total_prefix=None, font_pt=9):
|
|
60
|
+
"""Simple table document; QTextDocument renders it to PDF. Total rows are bold."""
|
|
61
|
+
|
|
62
|
+
def cell(v):
|
|
63
|
+
if isinstance(v, Decimal):
|
|
64
|
+
color = NEG_HTML if v < 0 else POS_HTML if v > 0 else None
|
|
65
|
+
style = f' style="color:{color}"' if color else ""
|
|
66
|
+
return f'<td align="right" nowrap{style}>{_money(v)}</td>'
|
|
67
|
+
align = ' align="right"' if isinstance(v, int) else ""
|
|
68
|
+
return f"<td nowrap{align}>{html.escape(str(v))}</td>"
|
|
69
|
+
|
|
70
|
+
parts = [f'<body style="font-size:{font_pt}pt">', f"<h2>{html.escape(title)}</h2>"]
|
|
71
|
+
parts += [f'<p style="margin:0">{html.escape(n)}</p>' for n in notes]
|
|
72
|
+
parts.append('<br><table border="1" cellspacing="0" cellpadding="3" style="border-collapse:collapse">')
|
|
73
|
+
parts.append("<tr>" + "".join(f"<th nowrap>{html.escape(h)}</th>" for h in headers) + "</tr>")
|
|
74
|
+
for row in rows:
|
|
75
|
+
bold = total_prefix and isinstance(row[0], str) and row[0].startswith(total_prefix)
|
|
76
|
+
tr = "".join(cell(v) for v in row)
|
|
77
|
+
parts.append(f'<tr style="font-weight:bold">{tr}</tr>' if bold else f"<tr>{tr}</tr>")
|
|
78
|
+
parts.append("</table></body>")
|
|
79
|
+
return "\n".join(parts)
|