180 lines
7.9 KiB
Python
180 lines
7.9 KiB
Python
|
#!/bin/env python3
|
||
|
|
||
|
import argparse
|
||
|
import csv
|
||
|
import datetime
|
||
|
import decimal
|
||
|
import email.message
|
||
|
import getpass
|
||
|
import pathlib
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
|
||
|
import jinja2
|
||
|
|
||
|
import triad.client
|
||
|
|
||
|
class Env(object):
|
||
|
jinja2_env: jinja2.Environment
|
||
|
args: argparse.Namespace
|
||
|
session: triad.client.Session
|
||
|
|
||
|
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.writeheader()
|
||
|
invoicing_client = triad.client.InvoicingClient.from_session(env.session)
|
||
|
template = env.jinja2_env.get_template("beitrag.eml")
|
||
|
for member in csvreader:
|
||
|
invoice = invoicing_client.get_invoice_by_id(member['invoice_id'])
|
||
|
year = invoice.invoice_date or env.args.year
|
||
|
months = [datetime.date(year.year, month, 1) for month in range(1, 13)]
|
||
|
email_address = member.get('email') or invoice.party.email
|
||
|
try:
|
||
|
sepa_mandate = invoice.party.reception_direct_debits[0]
|
||
|
except IndexError:
|
||
|
sepa_mandate = None
|
||
|
else:
|
||
|
sepa_mandate = sepa_mandate.sepa_mandate
|
||
|
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,
|
||
|
'sepa_mandate': sepa_mandate,
|
||
|
'payments': [],
|
||
|
'sender': env.args.email_sender,
|
||
|
"year": year,
|
||
|
"fee_months": [datetime.date(year.year, month, 1) for month in range(1, 13)]
|
||
|
}
|
||
|
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.set_content(template.render(**template_args))
|
||
|
with (env.args.output_dir / member['message_id']).open("wb") as mailfile:
|
||
|
mailfile.write(bytes(msg))
|
||
|
csvwriter.writerow(member)
|
||
|
|
||
|
def sepa_mandate(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.writeheader()
|
||
|
client = triad.client.BaseClient.from_session(env.session)
|
||
|
template = env.jinja2_env.get_template("sepa_mandat.eml")
|
||
|
for mandate in csvreader:
|
||
|
email_address= mandate.get('email')
|
||
|
if 'sepa_mandate_ref' in mandate:
|
||
|
sepa_mandate = client.get_model('account.payment.sepa.mandate').find([('identification', '=', mandate['sepa_mandate_ref'])])
|
||
|
party = sepa_mandate.party
|
||
|
elif 'party_id' in mandate:
|
||
|
party = client.get_model('party.party').find([('code', '=', mandate['party_id'])])[0]
|
||
|
email_address = email_address or party.email
|
||
|
sepa_mandate = party.reception_direct_debits[0].sepa_mandate
|
||
|
else:
|
||
|
continue
|
||
|
template_args = {
|
||
|
"party": party,
|
||
|
"sepa_mandate": sepa_mandate
|
||
|
}
|
||
|
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"SEPA-Lastschriftmandat MREF: {sepa_mandate.identification}"
|
||
|
msg["Message-Id"] = email.utils.make_msgid(domain="verein.ccchb.de")
|
||
|
mandate['message_id'] = msg["Message-Id"]
|
||
|
msg.set_content(template.render(**template_args))
|
||
|
if env.args.pdf:
|
||
|
msg.add_attachment(sepa_mandate_build_pdf(env, sepa_mandate), 'application', 'pdf', filename='SEPA-Lastschriftmandat.pdf')
|
||
|
with (env.args.output_dir / mandate['message_id']).open("wb") as mailfile:
|
||
|
mailfile.write(bytes(msg))
|
||
|
csvwriter.writerow(mandate)
|
||
|
|
||
|
def sepa_mandate_build_pdf(env, sepa_mandate):
|
||
|
with tempfile.TemporaryDirectory() as tempdir:
|
||
|
tempdir_path = pathlib.Path(tempdir)
|
||
|
jobname = sepa_mandate.identification
|
||
|
with (tempdir_path / (jobname + "-settings.tex")).open("w") as fh:
|
||
|
fh.write(f'''
|
||
|
\\csdef{{sepa-m001}}{{{sepa_mandate.identification}}}
|
||
|
\\csdef{{sepa-m006}}{{Wiederkehrende Zahlung}}
|
||
|
\\csdef{{sepa-p001}}{{{sepa_mandate.party.name}}}
|
||
|
\\csdef{{sepa-d001}}{{{sepa_mandate.account_number.number}}}
|
||
|
\\csdef{{sepa-m008}}{{{sepa_mandate.signature_date}}}
|
||
|
\\csdef{{sepa-m009}}{{\\emph{{entfällt}}}}
|
||
|
\\csdef{{sepa-d002a}}{{}}
|
||
|
\\csdef{{sepa-d002b}}{{}}
|
||
|
\\csdef{{sepa-p005}}{{}}
|
||
|
\\csdef{{sepa-remarks}}{{Dieses Mandat ist eine Abschrift des dem Zahlungsempfänger vorliegenden, originalen SEPA-Lastschriftmandats.\\\\
|
||
|
Erstellt am {datetime.date.today()}}}
|
||
|
''')
|
||
|
compile_latex(jobname, env.args.latex_template, tempdir)
|
||
|
with (tempdir_path / (jobname + ".pdf")).open("rb") as fh:
|
||
|
return fh.read()
|
||
|
|
||
|
def compile_latex(jobname, texfile, directory, debug=True):
|
||
|
latexmk_args = ["latexmk", "-lualatex", str(texfile), f"-jobname={jobname}"]
|
||
|
if not debug:
|
||
|
latexmk_args.append("-silent")
|
||
|
subprocess.run(latexmk_args, cwd=directory)
|
||
|
|
||
|
def main():
|
||
|
parser = argparse.ArgumentParser()
|
||
|
|
||
|
parser.add_argument("-U", "--uri", help="Tryton URI")
|
||
|
parser.add_argument("-u", "--username", help="Tryton username")
|
||
|
parser.add_argument("-p", "--password", help="Tryton password")
|
||
|
|
||
|
parser.add_argument("-O", "--output-dir", help="email output directory", default=".", type=pathlib.Path)
|
||
|
parser.add_argument("-T", "--template-dir", help="email template directory", default="mail_templates")
|
||
|
|
||
|
# Email arguments
|
||
|
parser.add_argument('--email-sender', help='email signature sender')
|
||
|
parser.add_argument("--email-from", help="email from header")
|
||
|
parser.add_argument("--email-date", help="email date header")
|
||
|
|
||
|
subparsers = parser.add_subparsers()
|
||
|
|
||
|
fee_invoice_parser = subparsers.add_parser("fee-invoice")
|
||
|
fee_invoice_parser.add_argument("-o", "--output", type=argparse.FileType("w"), default='-')
|
||
|
fee_invoice_parser.add_argument("-F", "--csv-fields", type=lambda f: f.split(","), help="csv fields")
|
||
|
fee_invoice_parser.add_argument("-y", "--year", type=lambda y: datetime.date(int(y), 1, 1))
|
||
|
fee_invoice_parser.add_argument("--months", help="Beitragsmonate", type=lambda m: m.split(","), default="1,2,3,4,5,6,7,8,9,10,11,12")
|
||
|
fee_invoice_parser.add_argument("csvfile", type=argparse.FileType("r"), default="-", nargs="?")
|
||
|
fee_invoice_parser.set_defaults(func=fee_invoice)
|
||
|
|
||
|
sepa_mandate_parser = subparsers.add_parser('sepa-mandate')
|
||
|
sepa_mandate_parser.add_argument('-o', '--output', type=argparse.FileType("w"), default='-')
|
||
|
sepa_mandate_parser.add_argument('-F', '--csv-fields', type=lambda f: f.split(','), help='csv fields')
|
||
|
sepa_mandate_parser.add_argument('--pdf', action='store_true', help='build pdf attachment', default=False)
|
||
|
sepa_mandate_parser.add_argument('--no-pdf', action='store_false', dest='pdf', help='don\'t build pdf attachment')
|
||
|
sepa_mandate_parser.add_argument('--latex-template', help='latex template in $TEXINPUTS')
|
||
|
sepa_mandate_parser.add_argument('csvfile', type=argparse.FileType('r'), default='-', nargs='?')
|
||
|
sepa_mandate_parser.set_defaults(func=sepa_mandate)
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
env = Env()
|
||
|
env.jinja2_env = jinja2.Environment(
|
||
|
loader=jinja2.FileSystemLoader(args.template_dir)
|
||
|
)
|
||
|
env.args = args
|
||
|
|
||
|
if not args.uri:
|
||
|
args.uri = input("URI for Tryton: ")
|
||
|
|
||
|
if not args.username:
|
||
|
args.username = input("Username for Tryton: ")
|
||
|
|
||
|
if not args.password:
|
||
|
args.password = getpass.getpass(f"Password for Tryton user `{args.username}': ")
|
||
|
|
||
|
with triad.client.Session.start(args.uri, args.username, args.password) as session:
|
||
|
env.session = session
|
||
|
args.func(env)
|
||
|
|
||
|
main()
|