usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

commit b8567b7c3cf6496e15dbf619c950289448ba5293
parent 1ddf256a0693efae345a56fd8c4eefbe1274db90
Author: lash <dev@holbrook.no>
Date:   Sun,  5 Apr 2026 10:03:59 -0600

Handle ref and extref in entry edit, balancer on deserialize

Diffstat:
Mdummy/usawa/cli/entry.py | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdummy/usawa/entry.py | 4++--
Mdummy/usawa/store.py | 4++--
3 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/dummy/usawa/cli/entry.py b/dummy/usawa/cli/entry.py @@ -8,6 +8,7 @@ import mimetypes from usawa import Asset, Entry, EntryPart from usawa.account import Account, AccountDisplay, AccountType +from usawa.balance import Balancer logg = logging.getLogger('cli.entry') @@ -15,6 +16,26 @@ class AbortMenu(Exception): pass +def writehelp(w=sys.stdout): + s = """h: this help +i: input account +o: output account +y: ref +x: extref +r: reset accounts +d: description ++: add tag +-: remove tag +v: view matched attachment (in any) +s: skip entry +t: save entry to draft +w: write entry +q: quit +""" + w.write(s + "\n") + + + def choose_account(ctx, include_create=False): r = None accounts_r = [] @@ -36,6 +57,8 @@ def choose_account(ctx, include_create=False): i = int(v) except ValueError: logg.debug('Invalid number, going back to search') + a = v + if i != None: try: r = accounts_r[i] @@ -117,7 +140,7 @@ def handle_view(ctx, entry, o): ) -def handle_description(ctx, entry, v): +def handle_description(ctx, entry, v, prefix=None): v = input_or_default("description", entry.description) entry.description = v @@ -125,12 +148,28 @@ def handle_description(ctx, entry, v): def handle_reset(ctx, entry, v): entry.debit = [] entry.credit = [] + entry.balancer = Balancer(self.ctx.uidx) + + +def handle_ref(ctx, entry, v): + ref = None + label = 'ref' + if v == 'y': + ref = entry.ref + else: + ref = entry.extref + label = 'ext' + label + v = input_or_default(label, ref) + if v == 'y': + entry.ref = v + else: + entry.extref = v def try_entry_uuid(ctx, v): v = uuid.UUID(v) entry = Entry.empty(ref=str(v)) - return ctx.store.get_draft(entry) + return ctx.store.get_draft(entry, unitindex=ctx.uidx) def try_entry_serial(ctx, v): @@ -258,7 +297,7 @@ class EntrySession: self.part_side = 'dst' else: self.part_side = 'src' - v = input_or_default('Entry side', self.side) + v = input_or_default('Entry side', self.part_side) if v == '': return False k = parse_side(ctx, v) @@ -273,7 +312,13 @@ class EntrySession: account = Account.from_path(v) #amount = None - v = input_or_default('Account {} amount'.format(account)) + rem = 0 + if self.part_side == 'src': + rem = self._src_remaining() + else: + rem = self._dst_remaining() + rem = self.ctx.uidx.to_floatstring(account.sym, rem) + v = input_or_default('Account {} amount'.format(account), rem) amount = parse_amount(self.ctx, account.sym, v) isdebit = self.part_side=='src' @@ -311,8 +356,11 @@ class EntrySession: if v == 'v': handle_view(self.ctx, self.entry, v) return True + if v == 'x' or v == 'y': + handle_ref(self.ctx, self.entry, v) + return True if v == 'd': - handle_description(self.ctx, self.entry, v) + handle_description(self.ctx, self.entry, v, prefix=self.heading) return True if v == 'r': handle_reset(self.ctx, self.entry, v) @@ -342,16 +390,35 @@ class EntrySession: r = self.handle_input(v) + def check(self): + if self.entry.description == None: + raise ValueError('missing description') + + """ + :return: True to continue edit entry, false to end entry + """ def finalize(self): if not self.commit: logg.debug('skipping ' + str(self.entry)) return False + v = input('write: are you sure? (YES)') + if v != 'YES': + logg.debug('abort write ' + str(self.entry)) + return True + + try: + self.check() + except Exception as e: + logg.error('abort write on check error: ' + str(e)) + if self.final: + self.entry.description = self.get_description() self.entry.parent = self.ctx.ledger.cur self.entry.serial = self.ctx.ledger.next_serial() self.entry.sign(self.ctx.wallet) self.ctx.store.add_entry(self.entry, update_ledger=True) + self.ctx.store.put_draft(self.entry) self.ctx.ledger.truncate() self.ctx.ledger.sign() f = open(self.ctx.ledger_path_out, 'w') @@ -362,17 +429,19 @@ class EntrySession: else: self.ctx.store.put_draft(self.entry) - return True + return False def start(self, skip_first=False): if not skip_first: self.do_interactive_one() - try: - self.do_interactive_two() - except StopIteration: - return None - r = self.finalize() + r = True + while r: + try: + self.do_interactive_two() + except StopIteration: + return None + r = self.finalize() return self.entry diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -403,7 +403,7 @@ class Entry(UsawaElement): :rtype: usawa.Entry """ @staticmethod - def deserialize(data): + def deserialize(data, unitindex=None): v = rencode.loads(data) parent = v[1] serial = v[2] @@ -419,7 +419,7 @@ class Entry(UsawaElement): tags = Tags.deserialize(v[10]) except TypeError: pass - o = Entry(serial, date, ref=ref, description=description, parent=parent, tx_datereg=date_reg, tags=tags) + o = Entry(serial, date, ref=ref, description=description, parent=parent, tx_datereg=date_reg, tags=tags, unitindex=unitindex) super(Entry, o).deserialize(v[0]) for v in src_data: src = EntryPart.deserialize(v, debit=True) diff --git a/dummy/usawa/store.py b/dummy/usawa/store.py @@ -271,12 +271,12 @@ class EntryStore(KeyStore): self.db.put(k, v, exist_ok=True) - def get_draft(self, entry): + def get_draft(self, entry, unitindex=None): k = pfx_entry_draft(entry) v = self.db.get(k) if len(v) == 1: return None - entry = Entry.deserialize(v) + entry = Entry.deserialize(v, unitindex=unitindex) # TODO: hacky! i = 0 for o in entry.attachment: