2026-06-06 10:18:15 +02:00
|
|
|
"""Data mapping functions.
|
|
|
|
|
|
|
|
|
|
Replaces mappings.js — transforms raw DB rows into API response shapes.
|
|
|
|
|
|
|
|
|
|
Mappers:
|
|
|
|
|
NONE, CONTRACT, CONTRACTLIST, DEBIT, DEBITLIST, CONTRIBUTIONS,
|
|
|
|
|
MEMBER, MEMO, MEMBERLIST, MEMBERLIST_TO_LDAPCSV,
|
|
|
|
|
WITHDRAWAL, WITHDRAWALLIST
|
|
|
|
|
"""
|
|
|
|
|
|
2026-06-08 20:33:47 +02:00
|
|
|
import os.path
|
2026-06-06 10:18:15 +02:00
|
|
|
|
2026-06-08 20:33:47 +02:00
|
|
|
import memberdata
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def none_mapper(ctx):
|
|
|
|
|
"""Identity mapper — returns context unchanged."""
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
2026-06-08 20:33:47 +02:00
|
|
|
# ── Contract mappers ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _build_contract_row(row, base_url, path_prefix):
|
|
|
|
|
"""Build a single contract dict from a raw DB row."""
|
|
|
|
|
newrow = {
|
|
|
|
|
'Vertragsnummer': int(row['VertragNr']),
|
|
|
|
|
'Vertragsart': row['ArtName'],
|
|
|
|
|
'Sollstellung': row['Sollstellung'],
|
|
|
|
|
'Betrag': row['Betrag'],
|
|
|
|
|
'Vertragsbeginn': memberdata.datum_parsed(row['VertragBegin']),
|
|
|
|
|
}
|
|
|
|
|
if row.get('VertragEnde'):
|
|
|
|
|
newrow['Vertragsende'] = memberdata.datum_parsed(row['VertragEnde'])
|
|
|
|
|
|
|
|
|
|
Verwendungszweck = []
|
|
|
|
|
for field in ('VerwZw1', 'VerwZw2', 'VerwZw3', 'VerwZw4'):
|
|
|
|
|
val = row.get(field)
|
|
|
|
|
if val and val != '':
|
|
|
|
|
Verwendungszweck.append(val)
|
|
|
|
|
if Verwendungszweck:
|
|
|
|
|
newrow['Verwendungszweck'] = Verwendungszweck
|
|
|
|
|
|
|
|
|
|
# URL is set by the caller based on whether this is a list or single item
|
|
|
|
|
return newrow
|
|
|
|
|
|
|
|
|
|
|
2026-06-06 10:18:15 +02:00
|
|
|
def contract_mapper(ctx):
|
|
|
|
|
"""Map a single contract record."""
|
2026-06-08 20:33:47 +02:00
|
|
|
data = ctx.get('data', [])
|
|
|
|
|
if len(data) != 1:
|
|
|
|
|
ctx['data'] = {}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
newrow = _build_contract_row(
|
|
|
|
|
data[0],
|
|
|
|
|
ctx['config'].get('base', ''),
|
|
|
|
|
ctx['request'].path,
|
|
|
|
|
)
|
|
|
|
|
newrow['url'] = ctx['config'].get('base', '') + ctx['request'].path
|
|
|
|
|
ctx['data'] = newrow
|
|
|
|
|
return ctx
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def contractlist_mapper(ctx):
|
|
|
|
|
"""Map a list of contract records into paginated format."""
|
2026-06-08 20:33:47 +02:00
|
|
|
results = []
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
newrow = _build_contract_row(row, base, path)
|
|
|
|
|
vertrag_nr = newrow['Vertragsnummer']
|
|
|
|
|
newrow['url'] = base + path.rstrip('*') + str(vertrag_nr)
|
|
|
|
|
results.append(newrow)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = {
|
|
|
|
|
'count': len(results),
|
|
|
|
|
'next': None,
|
|
|
|
|
'prev': None,
|
|
|
|
|
'results': results,
|
|
|
|
|
}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Debit mappers ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _build_debit_row(row, base_url, path_prefix):
|
|
|
|
|
"""Build a single debit dict from a raw DB row."""
|
|
|
|
|
newrow = {
|
|
|
|
|
'Vertragsnummer': int(row['VertragNr']),
|
|
|
|
|
'Vertrag': base_url + os.path.normpath(path_prefix + '../contract/') + str(int(row['VertragNr'])),
|
|
|
|
|
'Jahr': row['Jahr'],
|
|
|
|
|
'Monat': row['Monat'],
|
|
|
|
|
'Art': row['ArtName'],
|
|
|
|
|
'Datum': memberdata.datum_parsed(row['Datum']),
|
|
|
|
|
'Betrag': row.get('Betrag') or 0,
|
|
|
|
|
'Bezahlt': row.get('Bezahlt') or 0,
|
|
|
|
|
'Offen': row.get('Offen') or 0,
|
|
|
|
|
}
|
|
|
|
|
if row.get('GUID'):
|
|
|
|
|
newrow['url'] = base_url + path_prefix + row['GUID']
|
|
|
|
|
return newrow
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def debit_mapper(ctx):
|
|
|
|
|
"""Map a single debit record."""
|
2026-06-08 20:33:47 +02:00
|
|
|
data = ctx.get('data', [])
|
|
|
|
|
if len(data) != 1:
|
|
|
|
|
ctx['data'] = {}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
ctx['data'] = _build_debit_row(data[0], base, path)
|
|
|
|
|
return ctx
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def debitlist_mapper(ctx):
|
|
|
|
|
"""Map a list of debit records into paginated format."""
|
2026-06-08 20:33:47 +02:00
|
|
|
results = []
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
newrow = _build_debit_row(row, base, path)
|
|
|
|
|
if row.get('GUID'):
|
|
|
|
|
clean_path = path.rstrip('*')
|
|
|
|
|
newrow['url'] = base + clean_path + row['GUID']
|
|
|
|
|
results.append(newrow)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = {
|
|
|
|
|
'count': len(results),
|
|
|
|
|
'next': None,
|
|
|
|
|
'prev': None,
|
|
|
|
|
'results': results,
|
|
|
|
|
}
|
|
|
|
|
return ctx
|
|
|
|
|
|
2026-06-06 10:18:15 +02:00
|
|
|
|
2026-06-08 20:33:47 +02:00
|
|
|
# ── Contributions mapper ─────────────────────────────────────────
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
def contributions_mapper(ctx):
|
|
|
|
|
"""Aggregate contributions (billed/paid/unpaid) across contracts and years."""
|
2026-06-08 20:33:47 +02:00
|
|
|
newdata = {
|
|
|
|
|
'contracts': [],
|
|
|
|
|
'years': {},
|
|
|
|
|
'total': {'billed': 0, 'paid': 0, 'unpaid': 0},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
vertrag_nr = int(row['VertragNr'])
|
|
|
|
|
art_name = row['ArtName']
|
|
|
|
|
|
|
|
|
|
# Find or create contract entry
|
|
|
|
|
old_contract = -1
|
|
|
|
|
for idx, c in enumerate(newdata['contracts']):
|
|
|
|
|
if c['Vertragsnummer'] == vertrag_nr and c['Art'] == art_name:
|
|
|
|
|
old_contract = idx
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if old_contract == -1:
|
|
|
|
|
c = {
|
|
|
|
|
'Vertragsnummer': vertrag_nr,
|
|
|
|
|
'Vertrag': base + os.path.normpath(path + '/../contract/') + str(vertrag_nr),
|
|
|
|
|
'Art': art_name,
|
|
|
|
|
'Summen': {},
|
|
|
|
|
'total': {'billed': 0, 'paid': 0, 'unpaid': 0},
|
|
|
|
|
}
|
|
|
|
|
newdata['contracts'].append(c)
|
|
|
|
|
old_contract = len(newdata['contracts']) - 1
|
|
|
|
|
|
|
|
|
|
c = newdata['contracts'][old_contract]
|
|
|
|
|
jahr = str(row['Jahr'])
|
|
|
|
|
|
|
|
|
|
# Ensure year buckets exist
|
|
|
|
|
if jahr not in c['Summen']:
|
|
|
|
|
c['Summen'][jahr] = {'billed': 0, 'paid': 0, 'unpaid': 0}
|
|
|
|
|
if jahr not in newdata['years']:
|
|
|
|
|
newdata['years'][jahr] = {'billed': 0, 'paid': 0, 'unpaid': 0}
|
|
|
|
|
|
|
|
|
|
c['Summen'][jahr]['billed'] += row['Betrag']
|
|
|
|
|
c['total']['billed'] += row['Betrag']
|
|
|
|
|
newdata['years'][jahr]['billed'] += row['Betrag']
|
|
|
|
|
newdata['total']['billed'] += row['Betrag']
|
|
|
|
|
|
|
|
|
|
c['Summen'][jahr]['paid'] += row.get('Bezahlt', 0)
|
|
|
|
|
c['total']['paid'] += row.get('Bezahlt', 0)
|
|
|
|
|
newdata['years'][jahr]['paid'] += row.get('Bezahlt', 0)
|
|
|
|
|
newdata['total']['paid'] += row.get('Bezahlt', 0)
|
|
|
|
|
|
|
|
|
|
c['Summen'][jahr]['unpaid'] += row.get('Offen', 0)
|
|
|
|
|
c['total']['unpaid'] += row.get('Offen', 0)
|
|
|
|
|
newdata['years'][jahr]['unpaid'] += row.get('Offen', 0)
|
|
|
|
|
newdata['total']['unpaid'] += row.get('Offen', 0)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = newdata
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Member mappers ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
# Field mapping: DB column → API key
|
|
|
|
|
_MEMBER_VARLIST = {
|
|
|
|
|
'Vorname': 'Vorname',
|
|
|
|
|
'Nachname': 'Nachname',
|
|
|
|
|
'Strasse': 'Strasse',
|
|
|
|
|
'PLZ': 'PLZ',
|
|
|
|
|
'Ort': 'Ort',
|
|
|
|
|
'Kurzname': 'Crewname',
|
|
|
|
|
'Firma4': 'Firma',
|
|
|
|
|
'Telefon1': 'Telefon',
|
|
|
|
|
'Bank1': 'Bank',
|
|
|
|
|
'BLZ1': 'BLZ',
|
|
|
|
|
'IBAN1': 'IBAN',
|
|
|
|
|
'BIC1': 'BIC',
|
|
|
|
|
'Konto1': 'Konto',
|
|
|
|
|
'mandatsrefenz': 'Mandatreferenz',
|
|
|
|
|
'Telefon3': 'Ext_Email',
|
|
|
|
|
'Zahlungsweise': 'Zahlungsweise',
|
|
|
|
|
'EinzugLastMandLiegtVor': 'Lastschriftmandat',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ZAHLUNGSART_MAP = {'B': 'Bar', 'L': 'Lastschrift', 'U': 'Überweisung'}
|
|
|
|
|
_GESCHLECHT_MAP = {'WEIBLICH': 'F', 'MÄNNLICH': 'M'}
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def member_mapper(ctx):
|
|
|
|
|
"""Map a single member record with all fields."""
|
2026-06-08 20:33:47 +02:00
|
|
|
data = ctx.get('data', [])
|
|
|
|
|
if len(data) != 1:
|
|
|
|
|
ctx['data'] = {}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
row = data[0]
|
|
|
|
|
newdata = {
|
|
|
|
|
'Eintritt': memberdata.datum_parsed(row.get('Eintritt')),
|
|
|
|
|
'Paten': memberdata.patenarray(row.get('Kontaktwoher', '')),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Straight field mappings
|
|
|
|
|
for db_key, api_key in _MEMBER_VARLIST.items():
|
|
|
|
|
val = row.get(db_key)
|
|
|
|
|
if val and val != '':
|
|
|
|
|
newdata[api_key] = val
|
|
|
|
|
|
|
|
|
|
# Optional date fields
|
|
|
|
|
if row.get('Geburtsdatum'):
|
|
|
|
|
newdata['Geburtsdatum'] = memberdata.datum_parsed(row['Geburtsdatum'])
|
|
|
|
|
if row.get('Austritt'):
|
|
|
|
|
newdata['Austritt'] = memberdata.datum_parsed(row['Austritt'])
|
|
|
|
|
|
|
|
|
|
# Payment method
|
|
|
|
|
zahlungsart = row.get('Zahlungsart')
|
|
|
|
|
if zahlungsart and zahlungsart in _ZAHLUNGSART_MAP:
|
|
|
|
|
newdata['Zahlungsart'] = _ZAHLUNGSART_MAP[zahlungsart]
|
|
|
|
|
|
|
|
|
|
# Gender
|
|
|
|
|
betreuung = row.get('Betreuung')
|
|
|
|
|
if betreuung and betreuung in _GESCHLECHT_MAP:
|
|
|
|
|
newdata['Geschlecht'] = _GESCHLECHT_MAP[betreuung]
|
|
|
|
|
|
|
|
|
|
# Numeric IDs
|
|
|
|
|
if row.get('AdrNr'):
|
|
|
|
|
newdata['Adressnummer'] = row['AdrNr']
|
|
|
|
|
if row.get('MITGLNR'):
|
|
|
|
|
newdata['Mitgliedsnummer'] = int(row['MITGLNR'])
|
|
|
|
|
|
|
|
|
|
# Status + URLs
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
newdata['Status'] = memberdata.realstatus(row)
|
|
|
|
|
newdata['url'] = base + path
|
|
|
|
|
newdata['Email'] = row.get('Kurzname', '') + '@c-base.org'
|
|
|
|
|
newdata['contracts'] = base + path + '/contract/'
|
|
|
|
|
newdata['debits'] = base + path + '/debit/'
|
|
|
|
|
newdata['withdrawals'] = base + path + '/withdrawal/'
|
|
|
|
|
newdata['contributions'] = base + path + '/contributions'
|
|
|
|
|
|
|
|
|
|
# Board-only memo link
|
|
|
|
|
flags = ctx.get('flags', [])
|
|
|
|
|
if '_board_' in flags:
|
|
|
|
|
newdata['memo'] = base + path + '/memo'
|
|
|
|
|
|
|
|
|
|
ctx['data'] = newdata
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _try_parse_rtf(rtf_text):
|
|
|
|
|
"""Best-effort RTF → plain text parser.
|
|
|
|
|
|
|
|
|
|
Attempts to use an available RTF library; falls back to stripping
|
|
|
|
|
RTF control words if nothing is installed. This keeps the app
|
|
|
|
|
working even without the optional dependency.
|
|
|
|
|
"""
|
|
|
|
|
html = None
|
|
|
|
|
try:
|
|
|
|
|
from rtfparse import RtfDoc
|
|
|
|
|
doc = RtfDoc(rtf_text)
|
|
|
|
|
# rtfparse gives plain text directly
|
|
|
|
|
parts = []
|
|
|
|
|
for block in doc.blocks:
|
|
|
|
|
if hasattr(block, 'content'):
|
|
|
|
|
parts.append(str(block.content))
|
|
|
|
|
else:
|
|
|
|
|
parts.extend(str(t.text) if hasattr(t, 'text') else '' for t in block.iter_text())
|
|
|
|
|
return ''.join(parts), html
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Fallback: strip obvious RTF control words (\pard, \plain, etc.)
|
|
|
|
|
import re
|
|
|
|
|
plain = rtf_text
|
|
|
|
|
plain = re.sub(r'\\+[a-zA-Z]+\{[^}]*\}', ' ', plain)
|
|
|
|
|
plain = re.sub(r'\\+[a-zA-Z]+', ' ', plain)
|
|
|
|
|
plain = plain.replace('{', '').replace('}', '')
|
|
|
|
|
return plain, html
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def memo_mapper(ctx):
|
|
|
|
|
"""Parse RTF memo and extract embedded JSON data."""
|
2026-06-08 20:33:47 +02:00
|
|
|
data = ctx.get('data', [])
|
|
|
|
|
if len(data) != 1:
|
|
|
|
|
ctx['data'] = {}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
row = data[0]
|
|
|
|
|
newdata = {'Date': memberdata.datum_parsed(row.get('Eintritt'))}
|
|
|
|
|
|
|
|
|
|
if row.get('Memotext') and row['Memotext'] != '':
|
|
|
|
|
newdata['RTF'] = row['Memotext']
|
|
|
|
|
if row.get('Benutzer'):
|
|
|
|
|
newdata['Editor'] = row['Benutzer']
|
|
|
|
|
|
|
|
|
|
# Parse RTF → plain text
|
|
|
|
|
if newdata.get('RTF'):
|
|
|
|
|
plain, html = _try_parse_rtf(newdata['RTF'])
|
|
|
|
|
if html:
|
|
|
|
|
newdata['HTML'] = html
|
|
|
|
|
newdata['plain'] = plain
|
|
|
|
|
|
|
|
|
|
# Extract embedded JSON between markers
|
|
|
|
|
mark_begin = '-----BEGIN CTEWARD DATA-----'
|
|
|
|
|
mark_end = '-----END CTEWARD DATA-----'
|
|
|
|
|
dbegin = plain.find(mark_begin)
|
|
|
|
|
dend = plain.find(mark_end)
|
|
|
|
|
if dbegin >= 0 and dend >= 0:
|
|
|
|
|
raw_json = plain[dbegin + len(mark_begin): dend].strip()
|
|
|
|
|
try:
|
|
|
|
|
import json as _json
|
|
|
|
|
newdata['data'] = raw_json
|
|
|
|
|
newdata['json'] = _json.loads(raw_json)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
ctx['data'] = newdata
|
|
|
|
|
return ctx
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def memberlist_mapper(ctx):
|
|
|
|
|
"""Map a list of members into paginated format."""
|
2026-06-08 20:33:47 +02:00
|
|
|
results = []
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
newrow = {
|
|
|
|
|
'Crewname': row.get('Kurzname'),
|
|
|
|
|
'Adressnummer': row.get('AdrNr'),
|
|
|
|
|
'Status': memberdata.realstatus(row),
|
|
|
|
|
'Eintritt': memberdata.datum_parsed(row.get('Eintritt')),
|
|
|
|
|
'Paten': memberdata.patenarray(row.get('Kontaktwoher', '')),
|
|
|
|
|
}
|
|
|
|
|
if row.get('Nachname'):
|
|
|
|
|
newrow['Nachname'] = row['Nachname']
|
|
|
|
|
if row.get('Vorname'):
|
|
|
|
|
newrow['Vorname'] = row['Vorname']
|
|
|
|
|
|
|
|
|
|
kurzname = row.get('Kurzname') or ''
|
|
|
|
|
if kurzname and kurzname[0] != 'X':
|
|
|
|
|
newrow['url'] = base + path.rstrip('*') + kurzname
|
|
|
|
|
newrow['Email'] = kurzname + '@c-base.org'
|
|
|
|
|
|
|
|
|
|
if row.get('Austritt'):
|
|
|
|
|
newrow['Austritt'] = memberdata.datum_parsed(row['Austritt'])
|
|
|
|
|
if row.get('Telefon3') and row['Telefon3'] != '':
|
|
|
|
|
newrow['Ext_Email'] = row['Telefon3']
|
|
|
|
|
|
|
|
|
|
results.append(newrow)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = {
|
|
|
|
|
'count': len(results),
|
|
|
|
|
'next': None,
|
|
|
|
|
'prev': None,
|
|
|
|
|
'results': results,
|
|
|
|
|
}
|
|
|
|
|
return ctx
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def memberlist_to_ldapcsv_mapper(ctx):
|
|
|
|
|
"""Map member list to LDAP CSV export format."""
|
2026-06-08 20:33:47 +02:00
|
|
|
results = []
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
newrow = {
|
|
|
|
|
'Nachname': row.get('Nachname'),
|
|
|
|
|
'Vorname': row.get('Vorname'),
|
|
|
|
|
'Crewname': row.get('Kurzname'),
|
|
|
|
|
'Status': memberdata.realstatus(row),
|
|
|
|
|
'externe E-Mail': row.get('Telefon3'),
|
|
|
|
|
'Eintritt': memberdata.datum_parsed(row.get('Eintritt')),
|
|
|
|
|
'Paten': memberdata.cleanpaten(row.get('Kontaktwoher', '')),
|
|
|
|
|
'Weiteres': '',
|
|
|
|
|
}
|
|
|
|
|
results.append(newrow)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = results
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Withdrawal mappers ───────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _build_withdrawal_row(row, base_url, path_prefix):
|
|
|
|
|
"""Build a single withdrawal dict from a raw DB row."""
|
|
|
|
|
newrow = {
|
|
|
|
|
'Vertragsnummer': int(row['VertragNr']),
|
|
|
|
|
'Vertrag': base_url + os.path.normpath(path_prefix + '../contract/') + str(int(row['VertragNr'])),
|
|
|
|
|
'BLZ': row.get('Adr_BLZ') or '',
|
|
|
|
|
'Konto': row.get('Adr_Konto') or '',
|
|
|
|
|
'IBAN': row.get('IBAN') or '',
|
|
|
|
|
'BIC': row.get('BIC') or '',
|
|
|
|
|
'Mandat': row.get('MandatsNr') or '',
|
|
|
|
|
'Betrag': row.get('Betrag_EU', 0) or 0,
|
|
|
|
|
'Jahr': row['Jahr'],
|
|
|
|
|
'Monat': row['Zeitraum'],
|
|
|
|
|
'Datum': memberdata.datum_parsed(row.get('ErstDatum')),
|
|
|
|
|
}
|
|
|
|
|
Verwendungszweck = []
|
|
|
|
|
for field in ('Zweck_1', 'Zweck_2'):
|
|
|
|
|
val = row.get(field)
|
|
|
|
|
if val and val != '':
|
|
|
|
|
Verwendungszweck.append(val)
|
|
|
|
|
if Verwendungszweck:
|
|
|
|
|
newrow['Verwendungszweck'] = Verwendungszweck
|
|
|
|
|
|
|
|
|
|
if row.get('GUID'):
|
|
|
|
|
newrow['url'] = base_url + path_prefix + row['GUID']
|
|
|
|
|
return newrow
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def withdrawal_mapper(ctx):
|
|
|
|
|
"""Map a single withdrawal record."""
|
2026-06-08 20:33:47 +02:00
|
|
|
data = ctx.get('data', [])
|
|
|
|
|
if len(data) != 1:
|
|
|
|
|
ctx['data'] = {}
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
ctx['data'] = _build_withdrawal_row(data[0], base, path)
|
|
|
|
|
return ctx
|
2026-06-06 10:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def withdrawallist_mapper(ctx):
|
|
|
|
|
"""Map a list of withdrawal records into paginated format."""
|
2026-06-08 20:33:47 +02:00
|
|
|
results = []
|
|
|
|
|
base = ctx['config'].get('base', '')
|
|
|
|
|
path = ctx['request'].path
|
|
|
|
|
|
|
|
|
|
for row in ctx.get('data', []):
|
|
|
|
|
newrow = _build_withdrawal_row(row, base, path)
|
|
|
|
|
if row.get('GUID'):
|
|
|
|
|
clean_path = path.rstrip('*')
|
|
|
|
|
newrow['url'] = base + clean_path + row['GUID']
|
|
|
|
|
results.append(newrow)
|
|
|
|
|
|
|
|
|
|
ctx['data'] = {
|
|
|
|
|
'count': len(results),
|
|
|
|
|
'next': None,
|
|
|
|
|
'prev': None,
|
|
|
|
|
'results': results,
|
|
|
|
|
}
|
|
|
|
|
return ctx
|