rumwurschteln

This commit is contained in:
Fritz Grimpen 2024-10-04 11:57:49 +00:00
parent 99a7997818
commit 8b4760b92c
4 changed files with 69 additions and 13 deletions

View file

@ -1,19 +1,20 @@
Hallo {{ invoice.party.name }},
für deine ordentliche Mitgliedschaft im Chaos Computer Club Bremen e.V. ist ein Mitgliedsbeitrag fällig. Für das Beitragsjahr {{ year.year }} beträgt der monatliche Beitrag {{ monthly_fee }}. Dieser Beitrag ist jeweils zu Beginn des Monats fällig.
für deine ordentliche Mitgliedschaft im Chaos Computer Club Bremen e.V. ist ein Mitgliedsbeitrag fällig. Für das Beitragsjahr {{ year.year }} beträgt der monatliche Beitrag {{ monthly_fee|format_currency }}. Dieser Beitrag ist jeweils zu Beginn des Monats fällig.
Wir berechnen dir daher die folgenden Mitgliedsbeiträge:
{% for line in invoice.lines %}
{{ line.description.ljust(50) }} {{ "{: >.2f}".format(line.unit_price) }} €
{{ line.description.ljust(39) }} {{ (line.quantity|format_quantity).rjust(5) }} × {{ (line.unit_price|format_currency).rjust(9) }} = {{ (line.amount|format_currency).rjust(12) }}
{%- endfor %}
{{ "".ljust(72, '-') }}
{{ "Summe".ljust(59) }} {{ (invoice.total_amount|format_currency).rjust(12) }}
{% if sepa_mandate -%}
Wir werden deine Mitgliedsbeiträge per SEPA-Lastschrift von deinem Konto mit der IBAN {{ sepa_mandate.account_number.number }} zu den folgenden Terminen einziehen:
{% for payment in invoice.lines_to_pay if not payment.reconiciliation and payment.payable_receivable_balance %}
{{ payment.maturity_date }} {{ "{: >.2f}".format(payment.amount) }} €
Wir werden deine Mitgliedsbeiträge per SEPA-Lastschrift von deinem Konto mit der IBAN {{ sepa_mandate.account_number.number|strip_iban }} zu den folgenden Terminen einziehen:
{% for maturity_date, amount in payments %}
{{ maturity_date }} {{ (amount|format_currency).rjust(25) }}
{%- endfor %}
{% else -%}
{%- else -%}
Bitte überweise deine Mitgliedsbeiträge fristgerecht auf unser Konto:
Kontoinhaber: Chaos Computer Club Bremen e.V.
@ -22,9 +23,11 @@ Bank: GLS Gemeinschaftsbank eG
BIC: GENODEM1GLS
Verwendungszweck: Beitrag {{ invoice.party.name }}
Du kannst den Beitrag für das gesamte Jahr {{ year.year }} im Voraus überweisen.
Du kannst den Beitrag für das gesamte Jahr {{ year.year }} in Höhe von {{ invoice.total_amount|format_currency }} im Voraus überweisen.
{%- endif %}
Solltest du eine Zuwendungsbestätigung (§ 10b EStG) für deine gezahlten Beiträge benötigen, wende dich bitte per E-Mail an vorstand@ccchb.de.
Viele Grüße,
der Vorstand

View file

@ -18,6 +18,9 @@ class BaseClient(object):
# model_cls.__module__ = self.__class__.__module__
return model_cls
def get_wizard(self, name):
return proteus.Wizard(name, config=self.config)
@classmethod
def from_session(cls, session):
config = proteus.config.XmlrpcConfig(session.url, headers=session.auth_headers())

View file

@ -138,6 +138,6 @@ with triad.client.Session.start(args.url, args.username, args.password) as sessi
print(f'\tInvoice: {invoice.id} {invoice.description}', file=sys.stderr)
for line in invoice.lines:
print(f'\t\tLine: {line.description} {line.unit_price}', file=sys.stderr)
print(f'\tYearly fee: {invoice.total_amount}', file=sys.stderr)
print(f'\tTotal amount: {invoice.total_amount}', file=sys.stderr)
member['invoice_id'] = invoice.id
csvwriter.writerow(member)

View file

@ -5,23 +5,65 @@ import csv
import datetime
import decimal
import email.message
import functools
import getpass
import operator
import pathlib
import subprocess
import tempfile
import babel.numbers
import dateutil.relativedelta
import jinja2
import triad.client
def strip_iban(iban):
iban = [c for c in iban if not c.isspace()]
return "".join(iban[0:3] + ["X" for c in iban[3:-3]] + iban[-3:])
def format_currency(amount, currency='EUR', locale='de_DE'):
return babel.numbers.format_currency(amount, currency, locale=locale)
def format_quantity(amount, locale='de_DE'):
return babel.numbers.format_decimal(amount, locale=locale)
class Env(object):
jinja2_env: jinja2.Environment
args: argparse.Namespace
session: triad.client.Session
def deduce_payments(client, invoice):
total_amount = invoice.total_amount
remainder = total_amount
for line in invoice.payment_term.lines:
if line.type == 'fixed':
amount = line.amount
elif line.type == 'percent':
amount = remainder * line.ratio
elif line.type =='percent_on_total':
amount = total_amount * line.ratio
elif line.type == 'remainder':
amount = remainder
else:
continue
relativedeltas = (
dateutil.relativedelta.relativedelta(
day=relativedelta.day,
month=int(relativedelta.month.index) if relativedelta.month else None,
days=relativedelta.days,
weeks=relativedelta.weeks,
months=relativedelta.months,
weekday=int(relativedelta.weekday.index) if relativedelta.weekday else None
) for relativedelta in line.relativedeltas
)
date = functools.reduce(operator.add, relativedeltas, invoice.payment_term_date)
remainder -= amount
yield (date, amount)
def fee_invoice(env: Env):
csvreader = csv.DictReader(env.args.csvfile, fieldnames=env.args.csv_fields)
csvwriter = csv.DictWriter(env.args.output, fieldnames=csvreader.fieldnames + ["message_id"])
csvwriter = csv.DictWriter(env.args.output, fieldnames=csvreader.fieldnames + (["message_id"] if 'message_id' not in csvreader.fieldnames else []))
csvwriter.writeheader()
invoicing_client = triad.client.InvoicingClient.from_session(env.session)
template = env.jinja2_env.get_template("beitrag.eml")
@ -36,23 +78,28 @@ def fee_invoice(env: Env):
sepa_mandate = None
else:
sepa_mandate = sepa_mandate.sepa_mandate
if invoice.state in {"draft", "validated"} and sepa_mandate is not None:
payments = list(deduce_payments(invoicing_client, invoice))
else:
payments = [(move.maturity_date, move.amount) for move in invoice.lines_to_pay if not move.reconciliation and move.payable_receivable_balance != 0]
template_args = {
'monthly_fee': decimal.Decimal(member.get('monthly_fee') or '0.0'),
"yearly_fee": decimal.Decimal(member.get('yearly_fee') or '0.0'),
'invoice': invoice,
"payments": payments,
'sepa_mandate': sepa_mandate,
'payments': [],
'payments': payments,
'sender': env.args.email_sender,
"year": year,
"fee_months": [datetime.date(year.year, month, 1) for month in range(1, 13)]
}
member.setdefault('message_id', email.utils.make_msgid(domain='verein.ccchb.de'))
msg = email.message.EmailMessage()
msg["From"] = env.args.email_from
msg["To"] = email_address
msg["Date"] = env.args.email_date or datetime.datetime.now()
msg["Subject"] = f"Beitragsrechnung {year.year} ({invoice.number})"
msg["Message-Id"] = email.utils.make_msgid(domain="verein.ccchb.de")
member['message_id'] = msg["Message-Id"]
msg["Message-Id"] = member['message_id']
msg.set_content(template.render(**template_args))
with (env.args.output_dir / member['message_id']).open("wb") as mailfile:
mailfile.write(bytes(msg))
@ -161,6 +208,9 @@ def main():
env.jinja2_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(args.template_dir)
)
env.jinja2_env.filters["strip_iban"] = strip_iban
env.jinja2_env.filters["format_currency"] = format_currency
env.jinja2_env.filters["format_quantity"] = format_quantity
env.args = args
if not args.uri: