#!/bin/env python3 import argparse import csv import datetime import decimal import getpass import sys import triad.client argparser = argparse.ArgumentParser(description='Beitragsrechnungen für ein Jahr erzeugen') argparser.add_argument('-u', '--username', help='Tryton-Benutzername', default='fgrimpen') argparser.add_argument('-p', '--password', help='Tryton-Passwort') argparser.add_argument('-U', '--url', help='Tryton-URL', default='https://kasse.verein.ccchb.de/tryton/') argparser.add_argument('--reference-date', help='Referenzdatum für Zahlungsbedingung', type=datetime.date.fromisoformat) argparser.add_argument('--claims-account', help='Forderungskonto', default='12001') argparser.add_argument('--fee-account', help='Beitragskonto', default='40000') argparser.add_argument('--default-payment-term', default='1m') argparser.add_argument("-F", "--csv-fields", type=lambda f: f.split(","), help="csv fields") argparser.add_argument('-n', '--dry-run', action='store_true') argparser.add_argument("-o", "--output", help="output file", type=argparse.FileType("w"), default="-") argparser.add_argument("--update", help="update invoices", action="store_true") argparser.add_argument("--months", help="Beitragsmonate", type=lambda m: m.split(","), default="1,2,3,4,5,6,7,8,9,10,11,12") argparser.add_argument('year', help='Beitragsjahr', type=lambda s: datetime.date(int(s), 1, 1)) argparser.add_argument('csvfile', nargs='?', type=argparse.FileType('r'), default='-') args = argparser.parse_args() if not args.password: args.password = getpass.getpass(f"Password for {args.username}@{args.url}: ") def calculate_reference_date(date): today = datetime.date.today() if today >= date: date = today while date.day >= 10: date += datetime.timedelta(days=1) while date.day != 15: date += datetime.timedelta(days=1) return date if not args.reference_date: args.reference_date = calculate_reference_date(args.year) args.months = [datetime.date(args.year.year, int(m), 1) for m in args.months] csvreader = csv.DictReader(args.csvfile, fieldnames=args.csv_fields) csvwriter = csv.DictWriter(args.output, fieldnames=csvreader.fieldnames + ["invoice_id"]) csvwriter.writeheader() PAYMENT_TERMS_MAPPING = [ ({'1m'}, 'Beitrag monatlich/jährlich'), ({'3m'}, 'Beitrag quartalsweise/jährlich'), ({'6m'}, 'Beitrag halbjährlich/jährlich'), ({'1y', '12m'}, 'Beitrag jährlich/jährlich') ] def load_payment_terms(session, invoicing_client): ret = {} for keys, payment_term_name in PAYMENT_TERMS_MAPPING: payment_term = invoicing_client.PaymentTerm.find([('name', '=', payment_term_name)])[0] ret.update({key: payment_term for key in keys}) return ret def create_invoice(party, payment_term, monthly_fee, months, yearly_fee, claims_account, fee_account, invoicing_client, reference_date, year, invoice_id, update): if update: invoice = invoicing_client.Invoice(int(invoice_id)) else: invoice = invoicing_client.Invoice() invoice.type = 'out' invoice.party = party invoice.description = f"Beiträge {year.year}" invoice.account = claims_account invoice.payment_term = payment_term invoice.payment_term_date = reference_date if update: for line in invoice.lines: invoice.lines.remove(line) if monthly_fee: for month in months: invoice_line = invoicing_client.InvoiceLine() invoice_line.description = f"Beitrag {month.year}-{month.month}" invoice_line.account = fee_account invoice_line.quantity = decimal.Decimal(1) invoice_line.unit_price = decimal.Decimal(monthly_fee) invoice.lines.append(invoice_line) if yearly_fee: invoice_line = invoicing_client.InvoiceLine() invoice_line.description = f"Jahresbeitrag {year.year}" invoice_line.account = fee_account invoice_line.quantity = decimal.Decimal(1) invoice_line.unit_price = decimal.Decimal(yearly_fee) invoice.lines.append(invoice_line) invoice.state = 'draft' return invoice with triad.client.Session.start(args.url, args.username, args.password) as session: party_client = triad.client.PartyClient.from_session(session) invoicing_client = triad.client.InvoicingClient.from_session(session) accounting_client = triad.client.AccountingClient.from_session(session) claims_account = accounting_client.get_account_by_code(args.claims_account) print(f"Claims account: {claims_account.code} {claims_account.name}", file=sys.stderr) fee_account = accounting_client.get_account_by_code(args.fee_account) print(f'Fee account: {fee_account.code} {fee_account.name}', file=sys.stderr) payment_terms = load_payment_terms(invoicing_client, invoicing_client) for key, payment_term in payment_terms.items(): print(f"Payment term {key}: {payment_term.name}", file=sys.stderr) print(f'Fee year: {args.year}', file=sys.stderr) print(f'Reference date: {args.reference_date}', file=sys.stderr) for member in csvreader: party = party_client.get_party_by_code(member['party_id']) print(f"Party: {party.code} {party.name}", file=sys.stderr) try: payment_term = payment_terms[member['payment_term']] except KeyError: payment_term = payment_terms[args.default_payment_term] print(f"\tPayment term: {payment_term.name}", file=sys.stderr) try: monthly_fee = decimal.Decimal(member['monthly_fee']) except: print(f"\tInvalid monthly fee: {member['monthly_fee']}", file=sys.stderr) monthly_fee = None else: print(f"\tMonthly fee: {monthly_fee}", file=sys.stderr) try: yearly_fee = decimal.Decimal(member['yearly_fee']) except: print(f'\tInvalid yearly fee: {member['yearly_fee']}', file=sys.stderr) yearly_fee = None else: print(f'\tYearly fee: {yearly_fee}', file=sys.stderr) invoice = create_invoice(party, payment_term, monthly_fee, args.months, yearly_fee, claims_account, fee_account, invoicing_client, args.reference_date, args.year, member.get('invoice_id'), args.update) if not args.dry_run: invoice.save() 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'\tTotal amount: {invoice.total_amount}', file=sys.stderr) member['invoice_id'] = invoice.id csvwriter.writerow(member)