def posting_distrib(posting: Posting, weight: Decimal, total_weight: Decimal): units = amount_distrib(posting.units, weight, total_weight) cost = posting.cost if isinstance(cost, CostSpec): cost = costspec_distrib(cost, weight, total_weight) return posting._replace( units=units, cost=cost, )
def per_marked_posting(posting: Posting, config: Config, account_prefix: str, total_value: Amount): if posting.meta == None: return [posting] marks = metaset.get(posting.meta, config.mark_name) # 4.1. or skip if not marked. if len(marks) == 0: return [posting] # 5. Per mark, create a new posting. todo_absolute: List[Tuple[Amount, str]] = list() todo_percent: List[Tuple[float, str]] = list() todo_absent: List[str] = list() for mark in marks: parts = mark.split("-") account: str # 5.1. Apply defaults. if parts[0] == "": raise RuntimeError( 'Plugin "share" requires mark to contain account name, seperated with "-".' ) account = parts[0] if ":" in parts[0] else account_prefix + parts[0] new_accounts.add(account) if len(parts) > 1: if "%" in parts[1] or "p" in parts[1]: try: todo_percent.append(( float(parts[1].split("%")[0].split("p")[0]) / 100, account, )) except Exception: raise RuntimeError( 'Something wrong with relative fraction "{}", please use a dot, e.g. "33.33p".' .format(parts[1])) else: try: todo_absolute.append(( Amount( D(parts[1]).quantize(config.quantize), posting.units.currency, ), account, )) except Exception: raise RuntimeError( 'Something wrong with absolute fraction "{}", please use a dot, e.g. "2.50".' .format(parts[1])) else: todo_absent.append(account) total_shared_absolute = sum( [amount.number for amount, _ in todo_absolute], D(0).quantize(config.quantize), ) total_shared_relative = sum([percent for percent, _ in todo_percent]) if total_shared_absolute > abs(total_value.number): raise RuntimeError( "The posting can't share more than it's absolute value") if total_shared_relative > 1: raise RuntimeError("The posting can't share more percent than 100%.", ) if total_shared_absolute == abs( total_value.number) and total_shared_relative > 0: raise RuntimeError( "It doesn't make sense to split a remaining amount of zero.") if total_shared_relative == 1 and len(todo_absent) > 0: raise RuntimeError( "It doesn't make sense to further auto-split when amount is already split for full 100%." ) new_postings_inner = [] # 5.2. Handle absolute amounts first: mutate original posting's amount & create new postings. for amount, account in todo_absolute: posting = posting._replace(units=posting.units._replace( number=(posting.units.number - amount.number).quantize(config.quantize)), ) if config.meta_name is not None: posting = posting._replace( meta=metaset.add(posting.meta, config.meta_name, account + " " + amount.to_string())) new_postings_inner.append( Posting( account, units=posting.units._replace( number=(amount.number).quantize(config.quantize)), cost=posting.cost, price=None, flag=None, meta={} if config.meta_name is None else {config.meta_name: posting.account + " " + amount.to_string()}, )) # 5.3. Handle relative amounts second: create new postings. remainder = posting.units total = D(0) for percent, account in todo_percent: units = posting.units._replace( number=(D(float(remainder.number) * percent)).quantize(config.quantize)) total = total + units.number new_postings_inner.append( Posting( account, units=units, cost=posting.cost, price=None, flag=None, meta={} if config.meta_name is None else { config.meta_name: posting.account + " " + str(int(percent * 100)) + "% (" + units.to_string() + ")" }, )) if config.meta_name is not None: posting = posting._replace(meta=metaset.add( posting.meta, config.meta_name, account + " " + str(int(percent * 100)) + "% (" + units.to_string() + ")", )) # 5.4. Handle absent amounts third: create new postings. total_percent = sum(i for i, _ in todo_percent) percent = (1 - total_percent) / (1 + len(todo_absent)) for account in todo_absent: units = posting.units._replace( number=(D(float(remainder.number) * percent)).quantize(config.quantize)) total = total + units.number new_postings_inner.append( Posting( account, units=units, cost=posting.cost, price=None, flag=None, meta={} if config.meta_name is None else { config.meta_name: posting.account + " (" + str(int(percent * 100)) + "%, " + units.to_string() + ")" }, )) if config.meta_name is not None: posting = posting._replace(meta=metaset.add( posting.meta, config.meta_name, account + " (" + str(int(percent * 100)) + "%, " + units.to_string() + ")", )) # 5.5. Handle original posting last (mutate!). posting = posting._replace( units=posting.units._replace(number=(remainder.number - total).quantize(config.quantize)), meta=metaset.clear(posting.meta, config.mark_name), ) # if(posting.units.number > D(0)): # new_postings.append(posting) return [posting] + new_postings_inner