rumwurschteln
This commit is contained in:
parent
99a7997818
commit
8b4760b92c
4 changed files with 69 additions and 13 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue