commit 888bd804f949b8965051e911dda27262aa18dd7f
parent 81c0d5f521d6a98aa1cc48ac051d7748605b2aa0
Author: lash <dev@holbrook.no>
Date: Sun, 16 Nov 2025 15:47:38 +0000
Implement entry pairs
Diffstat:
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__))