From: Christian Heller Date: Fri, 13 Feb 2026 04:16:02 +0000 (+0100) Subject: For auto-balancing per comment instruction allow (even) distribution among multiple... X-Git-Url: https://plomlompom.com/repos/booking/%7B%7Bprefix%7D%7D/condition?a=commitdiff_plain;ds=sidebyside;p=ledgplom For auto-balancing per comment instruction allow (even) distribution among multiple balancers. --- diff --git a/src/ledgplom/ledger.py b/src/ledgplom/ledger.py index 6609d96..610b797 100644 --- a/src/ledgplom/ledger.py +++ b/src/ledgplom/ledger.py @@ -12,7 +12,7 @@ _INDENT_CHARS = {SPACE, '\t'} _SEP_ACCNAME_STEPS = ':' SEP_COMMENTS = ';' _PREFIX_INSTRUCTION = '#' -_ACC_MOD_INSTRUCTORS = {'description', 'balancer'} +_ACC_MOD_INSTRUCTORS = {'description', 'balancers'} class _Wealth(): @@ -66,7 +66,7 @@ class _Wealth(): class _Account: 'Combine name, position in tree of owner, and wealth of self + children.' description = '' - balancer = '' + _balancers: tuple[str, ...] = tuple() def __init__(self, parent: Optional[Self], basename: str) -> None: self._wealth_diffs: dict[int, _Wealth] = {} @@ -117,6 +117,15 @@ class _Account: + step_name) yield rebuilt_path, step_name + @property + def balancers(self) -> tuple[str, ...]: + "Accounts among which this account's amount be balanced out." + return self._balancers + + @balancers.setter + def balancers(self, balancers_str: str) -> None: + self._balancers = tuple(balancers_str.split()) + class _DatLine: 'Line of .dat file parsed into comments and machine-readable data.' @@ -737,7 +746,7 @@ class Ledger: accounts = self._calc_accounts(id_) if lines: return {'raw_gap_lines': [dl.raw for dl in block.gap_lines], - 'account_balancers': {k: v.balancer + 'account_balancers': {k: v.balancers for k, v in accounts.items()}, 'booking_lines': ([line.as_dict for line in block.booking.booking_lines] diff --git a/src/templates/edit_structured.js b/src/templates/edit_structured.js index 94459d2..2c524d2 100644 --- a/src/templates/edit_structured.js +++ b/src/templates/edit_structured.js @@ -21,7 +21,7 @@ eslint ], "max-lines": [ "error", - {"max": 394, "skipBlankLines": true, "skipComments": true} + {"max": 427, "skipBlankLines": true, "skipComments": true} ], "max-lines-per-function": [ "error", @@ -57,6 +57,7 @@ import { const BALANCERS_DEFAULT = {"": "?"}, + FACTOR_CENTS = 100, IDX_LAST = -1, IDX_PAST_INTRO_LINE = 1, IDX_START = 0, @@ -72,6 +73,7 @@ const LEN_INDENT_INPUT_DEFAULT = 2, LEN_INDENT_INPUT_MINIMUM = 1, LEN_INTRO_LINE = 4, + LEN_INVERSION_DECIMAL = 2, LEN_LEN_INDENT = 1, LEN_LINE_STEP = 1, LEN_STEP_INPUT_LEN_INDENT = 1, @@ -105,26 +107,47 @@ const taintAndUpdateForm = () => { }; const balance = (bookingLine) => { - let invertedAmount = TOK_NONE; - if (bookingLine.amount !== TOK_NONE) { - invertedAmount = `-${bookingLine.amount}`; - const doubleMinus = "--"; - if (invertedAmount.startsWith(doubleMinus)) { - invertedAmount = invertedAmount.slice(doubleMinus.length); - } + const + nameSteps = bookingLine.account.split(SEP_ACCNAME_STEPS), + toReturn = []; + let + balancers = [], + leftToInvert = null, + partInverted = TOK_NONE; + while (balancers.length === LEN_EMPTY) { + balancers = accountBalancers[nameSteps.join(SEP_ACCNAME_STEPS)] || []; + nameSteps.pop(); } - let balancer = ""; - const accNameSteps = bookingLine.account.split(SEP_ACCNAME_STEPS); - while (balancer === "") { - balancer = accountBalancers[accNameSteps.join(SEP_ACCNAME_STEPS)]; - accNameSteps.pop(); + if (bookingLine.amount && bookingLine.amount !== TOK_NONE) { + leftToInvert = Math.abs(bookingLine.amount); + partInverted = leftToInvert / balancers.length; } - return newBookingLine( - balancer, - invertedAmount, - bookingLine.currency, - LEN_INDENT_INPUT_BALANCED + const centsRound = (amt) => Math.round(FACTOR_CENTS * amt) / FACTOR_CENTS; + const signFixed = (amt + ) => (-Math.sign(bookingLine.amount) * amt).toFixed(LEN_INVERSION_DECIMAL); + Array.from(balancers).forEach( + (balancer, idx) => { + let amountInput = TOK_NONE; + if (partInverted !== TOK_NONE) { + if (idx === balancers.length + IDX_LAST) { + amountInput = signFixed(centsRound(leftToInvert)); + } else { + const rounded = centsRound(partInverted); + leftToInvert -= rounded; + amountInput = signFixed(rounded); + } + } + toReturn.push( + newBookingLine( + balancer, + amountInput, + bookingLine.currency, + LEN_INDENT_INPUT_BALANCED + ) + ); + } ); + return toReturn; }; const updateForm = () => { @@ -354,7 +377,7 @@ const updateForm = () => { () => bookingLines.splice( idx + LEN_LINE_STEP, LEN_EMPTY, - balance(bookingLine) + ...balance(bookingLine) ) ); addButton( @@ -410,7 +433,7 @@ const mirror = () => { bookingLines.splice( idx, LEN_EMPTY, - balance(bookingLines[idx - LEN_LINE_STEP]) + ...balance(bookingLines[idx - LEN_LINE_STEP]) ); } taintAndUpdateForm(); diff --git a/src/tests/full.edit_structured.0 b/src/tests/full.edit_structured.0 index 4f6ae3a..b812ba1 100644 --- a/src/tests/full.edit_structured.0 +++ b/src/tests/full.edit_structured.0 @@ -91,8 +91,7 @@ input.amount {
- - + | @@ -100,7 +99,6 @@ from to -
Gap: