akt-cli 0.4.0__tar.gz → 0.5.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.
- {akt_cli-0.4.0 → akt_cli-0.5.0}/PKG-INFO +10 -1
- {akt_cli-0.4.0 → akt_cli-0.5.0}/README.md +9 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/pyproject.toml +1 -1
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/coa.py +5 -1
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/commands.py +4 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/registry.py +6 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/resources.py +78 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/LICENSE +0 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/__init__.py +0 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/cli.py +0 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/client.py +0 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/config.py +0 -0
- {akt_cli-0.4.0 → akt_cli-0.5.0}/src/akt/output.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: akt-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: akt — a CLI toolbox to fully drive an Akaunting accounting instance
|
|
5
5
|
Keywords: akaunting,accounting,cli,invoices,bills,api
|
|
6
6
|
Author: AsyncAlchemist
|
|
@@ -171,6 +171,15 @@ akt tax create --name "Sales Tax" --rate 8.25
|
|
|
171
171
|
akt bank create --name "Business Checking" --number 1001 --currency-code USD
|
|
172
172
|
akt bank list
|
|
173
173
|
|
|
174
|
+
# Open a book with a prior-period opening balance. The Double-Entry module
|
|
175
|
+
# auto-posts an opening-balance journal entry dated to the account's creation
|
|
176
|
+
# date; --opening-balance-date re-dates it to the period boundary so it lands in
|
|
177
|
+
# the right financial year. Needs a positive --opening-balance.
|
|
178
|
+
akt bank create --name "Business Checking" --number 1001 --currency-code USD \
|
|
179
|
+
--opening-balance 5000 --opening-balance-date 2024-12-31
|
|
180
|
+
# Re-date the opening entry of an existing account:
|
|
181
|
+
akt bank update 3 --opening-balance-date 2024-12-31
|
|
182
|
+
|
|
174
183
|
# Invoice with line items (totals computed server-side; number auto-generated)
|
|
175
184
|
akt invoice create --contact 12 \
|
|
176
185
|
--item 'name=Consulting,price=150,quantity=10,item_id=2' \
|
|
@@ -150,6 +150,15 @@ akt tax create --name "Sales Tax" --rate 8.25
|
|
|
150
150
|
akt bank create --name "Business Checking" --number 1001 --currency-code USD
|
|
151
151
|
akt bank list
|
|
152
152
|
|
|
153
|
+
# Open a book with a prior-period opening balance. The Double-Entry module
|
|
154
|
+
# auto-posts an opening-balance journal entry dated to the account's creation
|
|
155
|
+
# date; --opening-balance-date re-dates it to the period boundary so it lands in
|
|
156
|
+
# the right financial year. Needs a positive --opening-balance.
|
|
157
|
+
akt bank create --name "Business Checking" --number 1001 --currency-code USD \
|
|
158
|
+
--opening-balance 5000 --opening-balance-date 2024-12-31
|
|
159
|
+
# Re-date the opening entry of an existing account:
|
|
160
|
+
akt bank update 3 --opening-balance-date 2024-12-31
|
|
161
|
+
|
|
153
162
|
# Invoice with line items (totals computed server-side; number auto-generated)
|
|
154
163
|
akt invoice create --contact 12 \
|
|
155
164
|
--item 'name=Consulting,price=150,quantity=10,item_id=2' \
|
|
@@ -74,7 +74,11 @@ def parse_coa(text: str) -> CoaConfig:
|
|
|
74
74
|
code = int(row["code"])
|
|
75
75
|
name = str(row["name"])
|
|
76
76
|
type_id = int(row["type_id"])
|
|
77
|
-
mirror =
|
|
77
|
+
mirror = row.get("mirror", True)
|
|
78
|
+
if not isinstance(mirror, bool):
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"account '{name}': 'mirror' must be a boolean (true/false), "
|
|
81
|
+
f"got {mirror!r} — did you quote it?")
|
|
78
82
|
category = str(row.get("category", name))
|
|
79
83
|
if code in seen_codes:
|
|
80
84
|
raise ValueError(f"duplicate code {code}")
|
|
@@ -56,6 +56,8 @@ def cmd_create(res: Resource, client: Client, ns: Any) -> int:
|
|
|
56
56
|
else:
|
|
57
57
|
payload = client.post(endpoint, body, type_scope=type_scope)
|
|
58
58
|
data = payload.get("data", payload) if isinstance(payload, dict) else payload
|
|
59
|
+
if res.post_write:
|
|
60
|
+
res.post_write(res, client, data, ns)
|
|
59
61
|
emit(data, as_json=True)
|
|
60
62
|
return 0
|
|
61
63
|
|
|
@@ -104,6 +106,8 @@ def cmd_update(res: Resource, client: Client, ns: Any) -> int:
|
|
|
104
106
|
body["remove_attachment"] = 1
|
|
105
107
|
payload = client.put(path, body, type_scope=type_scope)
|
|
106
108
|
data = payload.get("data", payload) if isinstance(payload, dict) else payload
|
|
109
|
+
if res.post_write:
|
|
110
|
+
res.post_write(res, client, data, ns)
|
|
107
111
|
emit(data, as_json=True)
|
|
108
112
|
return 0
|
|
109
113
|
|
|
@@ -13,6 +13,7 @@ from .resources import (
|
|
|
13
13
|
build_journal_update,
|
|
14
14
|
build_payment_create,
|
|
15
15
|
build_transfer_create,
|
|
16
|
+
redate_opening_balance,
|
|
16
17
|
resolve_payment_delete,
|
|
17
18
|
resolve_payment_update,
|
|
18
19
|
)
|
|
@@ -101,6 +102,10 @@ BANK = Resource(
|
|
|
101
102
|
f("type", "Account type", default="bank"),
|
|
102
103
|
f("currency-code", "Currency code", default="USD"),
|
|
103
104
|
f("opening-balance", "Opening balance", default=0),
|
|
105
|
+
f("opening-balance-date",
|
|
106
|
+
"Date for the opening-balance journal entry (YYYY-MM-DD). Re-dates the "
|
|
107
|
+
"entry the Double-Entry module auto-posts; needs a positive opening balance.",
|
|
108
|
+
dest="opening_balance_date", local=True),
|
|
104
109
|
f("bank-name", "Bank name"),
|
|
105
110
|
f("bank-phone", "Bank phone"),
|
|
106
111
|
f("bank-address", "Bank address"),
|
|
@@ -112,6 +117,7 @@ BANK = Resource(
|
|
|
112
117
|
("Enabled", "enabled"),
|
|
113
118
|
],
|
|
114
119
|
help="Bank / cash accounts",
|
|
120
|
+
post_write=redate_opening_balance,
|
|
115
121
|
)
|
|
116
122
|
|
|
117
123
|
CATEGORY = Resource(
|
|
@@ -12,6 +12,7 @@ import datetime as _dt
|
|
|
12
12
|
import json
|
|
13
13
|
import mimetypes
|
|
14
14
|
import os
|
|
15
|
+
import sys
|
|
15
16
|
from dataclasses import dataclass, field
|
|
16
17
|
from typing import Any, Callable
|
|
17
18
|
|
|
@@ -32,6 +33,7 @@ class Field:
|
|
|
32
33
|
default: Any = None # default applied on create when omitted
|
|
33
34
|
is_flag: bool = False # store_true boolean flag
|
|
34
35
|
choices: list[str] | None = None
|
|
36
|
+
local: bool = False # CLI flag only; never sent in the request body
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def f(name: str, help: str = "", **kw) -> Field:
|
|
@@ -69,6 +71,10 @@ class Resource:
|
|
|
69
71
|
# returns (path, type_scope) for update; lets document-linked payments use
|
|
70
72
|
# the nested route (the flat /transactions route 400s on any document_id)
|
|
71
73
|
update_resolver: Callable[["Resource", Client, str, dict], "tuple[str, str | None]"] | None = None
|
|
74
|
+
# runs after a successful create/update on the /api path, with the returned
|
|
75
|
+
# record + the argparse namespace; used by bank to re-date the auto-posted
|
|
76
|
+
# opening-balance journal entry.
|
|
77
|
+
post_write: Callable[["Resource", Client, dict, Any], None] | None = None
|
|
72
78
|
|
|
73
79
|
def contact_scope(self) -> str:
|
|
74
80
|
"""ACL scope of the contact tied to a document resource."""
|
|
@@ -205,6 +211,8 @@ def body_from_fields(res: Resource, ns: Any, *, for_update: bool,
|
|
|
205
211
|
"""
|
|
206
212
|
body: dict[str, Any] = {}
|
|
207
213
|
for fld in res.fields:
|
|
214
|
+
if fld.local:
|
|
215
|
+
continue
|
|
208
216
|
val = getattr(ns, fld.dest, None)
|
|
209
217
|
if fld.is_flag:
|
|
210
218
|
# tri-state: only include if explicitly toggled
|
|
@@ -733,6 +741,76 @@ def build_journal_update(res: Resource, client: Client, ns: Any, current: dict)
|
|
|
733
741
|
return body
|
|
734
742
|
|
|
735
743
|
|
|
744
|
+
# --------------------------------------------------------------------------
|
|
745
|
+
# opening-balance journal entry re-dating (bank post_write hook)
|
|
746
|
+
# --------------------------------------------------------------------------
|
|
747
|
+
|
|
748
|
+
def _journal_reput_body(current: dict, *, paid_at: str, journal_number: str) -> dict:
|
|
749
|
+
"""Full journal-entry PUT body that re-dates an existing entry, preserving
|
|
750
|
+
its ledgers (by id) and amounts. ``journal_number`` is supplied by the caller
|
|
751
|
+
(the server-created opening-balance entry carries none, but the API requires
|
|
752
|
+
one)."""
|
|
753
|
+
body: dict[str, Any] = {
|
|
754
|
+
"paid_at": paid_at,
|
|
755
|
+
"journal_number": journal_number,
|
|
756
|
+
"description": current.get("description"),
|
|
757
|
+
"basis": current.get("basis") or "accrual",
|
|
758
|
+
"currency_code": current.get("currency_code") or "USD",
|
|
759
|
+
"currency_rate": current.get("currency_rate", 1) or 1,
|
|
760
|
+
"amount": 0,
|
|
761
|
+
"items": _journal_items_from_current(current),
|
|
762
|
+
}
|
|
763
|
+
if current.get("reference"):
|
|
764
|
+
body["reference"] = current["reference"]
|
|
765
|
+
return body
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _find_opening_balance_je(client: Client, record: dict) -> dict | None:
|
|
769
|
+
"""Locate the opening-balance journal entry the Double-Entry module auto-posts
|
|
770
|
+
for a bank/cash account. It carries ``reference = "opening-balance:<coa_id>"``
|
|
771
|
+
and ``description`` ending ``";<account name>"``. The chart-of-accounts API
|
|
772
|
+
doesn't expose the bank<->coa link, so match on those two fields and, if an
|
|
773
|
+
account name is duplicated, take the newest by ``created_at``."""
|
|
774
|
+
name = str(record.get("name") or "")
|
|
775
|
+
suffix = ";" + name
|
|
776
|
+
matches = [
|
|
777
|
+
e for e in client.list("journal-entry", all_pages=True)
|
|
778
|
+
if str(e.get("reference") or "").startswith("opening-balance:")
|
|
779
|
+
and str(e.get("description") or "").endswith(suffix)
|
|
780
|
+
]
|
|
781
|
+
if not matches:
|
|
782
|
+
return None
|
|
783
|
+
matches.sort(key=lambda e: str(e.get("created_at") or ""), reverse=True)
|
|
784
|
+
return matches[0]
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def redate_opening_balance(res: "Resource", client: Client, record: dict, ns: Any) -> None:
|
|
788
|
+
"""post_write hook for bank create/update: re-date the auto-posted
|
|
789
|
+
opening-balance journal entry to ``--opening-balance-date``.
|
|
790
|
+
|
|
791
|
+
The Double-Entry module stamps that entry with the account's created_at and
|
|
792
|
+
exposes no date field, so we set the balance normally (keeping the Banking
|
|
793
|
+
register correct) and rewrite the entry's date here. Re-dating via the
|
|
794
|
+
journal-entry route also rewrites each ledger's issued_at (what reports
|
|
795
|
+
filter on), and the module's account-update path only touches ledger
|
|
796
|
+
amounts, so the date sticks across later edits."""
|
|
797
|
+
date = getattr(ns, "opening_balance_date", None)
|
|
798
|
+
if not date:
|
|
799
|
+
return
|
|
800
|
+
je = _find_opening_balance_je(client, record)
|
|
801
|
+
if je is None:
|
|
802
|
+
print(f"note: no opening-balance journal entry found for {res.noun} "
|
|
803
|
+
f"{record.get('id')}; --opening-balance-date had no effect "
|
|
804
|
+
f"(opening balance is 0, or the Double-Entry module / owners-"
|
|
805
|
+
f"contribution account is not configured)", file=sys.stderr)
|
|
806
|
+
return
|
|
807
|
+
number = je.get("journal_number") or _next_journal_number(client)
|
|
808
|
+
body = _journal_reput_body(je, paid_at=_normalize_date(date), journal_number=number)
|
|
809
|
+
client.put(f"journal-entry/{je['id']}", body)
|
|
810
|
+
print(f"note: re-dated opening-balance journal entry {je['id']} to {date}",
|
|
811
|
+
file=sys.stderr)
|
|
812
|
+
|
|
813
|
+
|
|
736
814
|
# --------------------------------------------------------------------------
|
|
737
815
|
# chart-of-accounts (double-entry) — web-surface CRUD body builder
|
|
738
816
|
# --------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|