usawa

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | Submodules | LICENSE

commit 888bd804f949b8965051e911dda27262aa18dd7f
parent 81c0d5f521d6a98aa1cc48ac051d7748605b2aa0
Author: lash <dev@holbrook.no>
Date:   Sun, 16 Nov 2025 15:47:38 +0000

Implement entry pairs

Diffstat:
Mdummy/create.py | 2+-
Mdummy/schema.xsd | 19++++++++++++++++---
Mdummy/svcontas/__init__.py | 2+-
Mdummy/svcontas/entry.py | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mdummy/svcontas/ledger.py | 35++++++++++++++++++++++++++---------
Mdummy/svcontas/xml.py | 2++
6 files changed, 105 insertions(+), 34 deletions(-)

diff --git a/dummy/create.py b/dummy/create.py @@ -5,7 +5,7 @@ import lxml.etree import confini import nacl.signing -from svcontas import Ledger, Entry, DemoWallet, ACL, get_units, init_ledger +from svcontas import Entry, DemoWallet, ACL, get_units, init_ledger seed = bytes.fromhex('2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae') diff --git a/dummy/schema.xsd b/dummy/schema.xsd @@ -95,7 +95,6 @@ <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> </xs:sequence> <!-- TODO: attribute enum --> - <xs:attribute name="type" type="xs:string" /> </xs:complexType> <xs:complexType name="EntryData"> @@ -106,8 +105,22 @@ <xs:element name="unit" type="xs:string" /> <xs:element name="date" type="xs:date" /> <xs:element name="dateTimeRegistered" type="xs:dateTime" /> - <xs:element name="account" type="xs:string" /> - <xs:element name="amount" type="xs:positiveInteger" /> + <xs:element name="src" type="EntryPart"/> + <xs:element name="dst" type="EntryPart"/> + <xs:element name="attachment" type="Attachment" minOccurs="1" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> + + <xs:complexType name="EntryPart"> + <xs:attribute name="type" type="xs:string" /> + <xs:element name="account" type="xs:string" /> + <xs:element name="amount" type="xs:positiveInteger" /> + </xs:complexType> + + <xs:complexType name="Attachment"> + <xs:attribute name="mime" type="xs:string" /> + <xs:element name="slug" type="xs:string" /> + <xs:element name="description" type="xs:string" /> + <xs:element name="digest" type="Digest" /> + </xs:complexType> </xs:schema> diff --git a/dummy/svcontas/__init__.py b/dummy/svcontas/__init__.py @@ -1,7 +1,7 @@ from lxml import etree from .ledger import Ledger -from .entry import Entry +from .entry import Entry, EntryPart from .crypto import DemoWallet, ACL from .state import State from .xml import nsmap diff --git a/dummy/svcontas/entry.py b/dummy/svcontas/entry.py @@ -12,11 +12,51 @@ from .xml import nsmap logg = logging.getLogger('svcontas.entry') +class EntryPart: + + def __init__(self, typ, account, amount, src=False): + self.typ = typ + self.account = account + self.amount = amount + self.issrc = src + + + @staticmethod + def from_tree(tree, src=False): + typ = tree.get('type') + amount = int(tree.find('amount', namespaces=nsmap()).text) + account = tree.find('account', namespaces=nsmap()).text + return EntryPart(typ, account, amount, src=src) + + + def apply_tree(self, tree): + tag = 'dst' + if self.issrc: + tag = 'src' + part = etree.Element(tag, type=self.typ) + o = etree.Element('account') + o.text = self.account + part.append(o) + + o = etree.Element('amount') + o.text = str(self.amount) + part.append(o) + + tree.append(part) + return part + + + def __str__(self): + pfx = 'dst' + if self.issrc: + pfx = 'src' + return '[{}] {}:{} {}'.format(pfx, self.typ, self.account, self.amount) + + class Entry: # TODO: parent only 0 if serial 0 - def __init__(self, typ, amount, unit, serial, account, tx_date, ref=None, description=None, parent=None, tx_datereg=None): - self.typ = typ + def __init__(self, src, dst, unit, serial, tx_date, ref=None, description=None, parent=None, tx_datereg=None): if isinstance(parent, str): parent = bytes.fromhex(parent) elif parent == None: @@ -27,10 +67,8 @@ class Entry: ref = str(uuid.uuid4()) self.ref = ref self.parent = parent - self.amount = amount self.unit = unit self.serial = serial - self.account = account self.dt = tx_date if tx_datereg == None: tx_datereg = datetime.datetime.now() @@ -38,6 +76,8 @@ class Entry: self.attachment = [] self.sigs = {} self.description = description + self.src = src + self.dst = dst def attach(self, mime, algo, digest, description=None, slug=None): @@ -51,10 +91,8 @@ class Entry: @staticmethod def from_tree(tree, unitindex): o = tree.find('data', namespaces=nsmap()) - amount = int(o.find('amount', namespaces=nsmap()).text) unit = unitindex.get(o.find('unit', namespaces=nsmap()).text) serial = int(o.find('serial', namespaces=nsmap()).text) - account = o.find('account', namespaces=nsmap()).text ref = o.find('ref', namespaces=nsmap()).text parent = o.find('parent', namespaces=nsmap()).text description = o.find('description', namespaces=nsmap()) @@ -62,6 +100,9 @@ class Entry: description = description.text dt = datetime.date.fromisoformat(o.find('date', namespaces=nsmap()).text) dtreg = datetime.datetime.strptime(o.find('dateTimeRegistered', namespaces=nsmap()).text, '%Y-%m-%dT%H:%M:%SZ') + src = EntryPart.from_tree(o.find('src', namespaces=nsmap())) + dst = EntryPart.from_tree(o.find('dst', namespaces=nsmap()), credit=True) + r = Entry(tree.get('type'), amount, unit, serial, account, dt, ref=ref, parent=parent, tx_datereg=dtreg, description=description) for sig in tree.iter(NSPREFIX + 'sig'): r.add_signature(sig.get('keyid'), bytes.fromhex(sig.text)) @@ -69,6 +110,10 @@ class Entry: def serialize(self): + #src = self.src.serialize() + #dst = self.dst.serialize() + src = [self.src.typ, self.src.account, self.src.amount] + dst = [self.dst.typ, self.dst.account, self.dst.amount] d = [ self.parent, self.serial, @@ -76,7 +121,8 @@ class Entry: self.dtreg.strftime('%Y%m%d%H%M%S'), self.dt.strftime('%Y%m%d'), self.unit, - self.amount, + src, + dst, ] logg.debug('serialize entry {}'.format(d)) return rencode.dumps(d) @@ -99,7 +145,8 @@ class Entry: def to_tree(self): - tree = etree.Element('entry', type=self.typ) + #tree = etree.Element('entry', type=self.typ) + tree = etree.Element('entry') data = etree.Element('data') o = etree.Element('parent') @@ -126,19 +173,14 @@ class Entry: o.text = self.dtreg.strftime('%Y-%m-%dT%H:%M:%SZ') data.append(o) - o = etree.Element('account') - o.text = self.account - data.append(o) - if self.description: o = etree.Element('description') - o.text = self.account + o.text = self.description data.append(o) - o = etree.Element('amount') - o.text = str(self.amount) - data.append(o) - + self.src.apply_tree(tree) + self.dst.apply_tree(tree) + tree.append(data) for k in self.sigs.keys(): @@ -147,6 +189,3 @@ class Entry: tree.append(o) return tree - - - diff --git a/dummy/svcontas/ledger.py b/dummy/svcontas/ledger.py @@ -4,7 +4,7 @@ import logging import lxml from .crypto import DemoWallet -from .xml import nsmap +from .xml import nsmap, XML_FORMAT_VERSION from .state import State from .constant import NSPREFIX, DEFAULTPARENT from .entry import Entry @@ -33,12 +33,10 @@ class RunningTotal: def income_delta(self, v): self.income += v - self.asset += v def expense_delta(self, v): self.expense += v - self.asset -= v def asset_delta(self, v): @@ -50,9 +48,23 @@ class RunningTotal: def apply_entry(self, entry): - fn = getattr(self, entry.typ + '_delta') - fn(entry.amount) - logg.debug('applied entry {} typ {} amount {} total {} balance {}'.format(entry.serial, entry.typ, entry.amount, getattr(self, entry.typ), self.unitindex.to_floatstring(self.sym, self.get_balance()))) + src = entry.src.typ + dst = entry.dst.typ + src_isbalance = src in ['liability', 'asset'] + dst_isbalance = dst in ['liability', 'asset'] + + src_amount = entry.src.amount + dst_amount = entry.dst.amount + if src_isbalance and dst_isbalance: + if dst == 'liability': + src_amount *= -1 + dst_amount *= -1 + fn = getattr(self, entry.src.typ + '_delta') + fn(src_amount) + fn = getattr(self, entry.dst.typ + '_delta') + fn(dst_amount) + + logg.debug('applied entry {} src {} dst {} balance {}'.format(entry.serial, entry.src, entry.dst, self.unitindex.to_floatstring(self.sym, self.get_balance()))) def __str__(self): @@ -61,7 +73,7 @@ class RunningTotal: class Ledger: - def __init__(self, serial, base, unitindex, tree=None, acl=None): + def __init__(self, unitindex, tree=None, acl=None, serial=0, base=DEFAULTPARENT): self.uidx = unitindex self.sigs = {} self.entries = {} @@ -72,12 +84,13 @@ class Ledger: self.state = State() self.state.poke(serial, base) self.acl = acl + self.last = 0 def reset(self, src='defalsify.org'): self.entries[self.uidx.base] = [] self.running[self.uidx.base] = RunningTotal(self.uidx.base, self.uidx) - self.tree = lxml.etree.XML('<ledger xmlns="http://svcontas.defalsify.org/"></ledger>') + self.tree = lxml.etree.XML('<ledger xmlns="http://svcontas.defalsify.org/" version="{}"></ledger>'.format(XML_FORMAT_VERSION)) #self.tree = lxml.etree.Element('ledger', nsmap=nsmap()) o = lxml.etree.SubElement(self.tree, NSPREFIX + 'retrieved', nsmap=nsmap()) o.text = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%SZ') @@ -194,7 +207,7 @@ class Ledger: part = tree.find('incoming', namespaces=nsmap()) serial = int(part.get('serial')) o = part.find('digest', namespaces=nsmap()).text # verify that is sha512 - r = Ledger(serial, bytes.fromhex(o), unitindex, tree=tree, acl=acl) + r = Ledger(unitindex, tree=tree, acl=acl, serial=serial, base=bytes.fromhex(o)) for sig in part.iter(NSPREFIX + 'sig'): keyid = sig.get('keyid') @@ -221,10 +234,14 @@ class Ledger: def apply_tree(self, tree): + start = self.state.serial + self.last = 0 for v in tree.iter(NSPREFIX + 'entry'): logg.debug('processing entry {}'.format(v)) o = Entry.from_tree(v, self.uidx) self.add_entry(o, modify_tree=False) + self.last = o.serial + logg.info('last entry from tree serial ' + str(self.last)) def to_tree(self): diff --git a/dummy/svcontas/xml.py b/dummy/svcontas/xml.py @@ -5,6 +5,8 @@ import lxml from .constant import NAMESPACES +XML_FORMAT_VERSION = 1 + script_dir = os.path.realpath(os.path.dirname(__file__))