2024-10-03 10:39:59 -05:00
|
|
|
#!/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')
|
2024-10-03 17:08:59 -05:00
|
|
|
argparser.add_argument("-F", "--csv-fields", type=lambda f: f.split(","), help="csv fields")
|
2024-10-03 10:39:59 -05:00
|
|
|
argparser.add_argument('-n', '--dry-run', action='store_true')
|
2024-10-03 17:08:59 -05:00
|
|
|
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")
|
2024-10-03 10:39:59 -05:00
|
|
|
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)
|
|
|
|
|
2024-10-03 17:08:59 -05:00
|
|
|
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()
|
2024-10-03 10:39:59 -05:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-10-03 17:08:59 -05:00
|
|
|
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()
|
2024-10-03 10:39:59 -05:00
|
|
|
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
|
2024-10-03 17:08:59 -05:00
|
|
|
if update:
|
|
|
|
for line in invoice.lines:
|
|
|
|
invoice.lines.remove(line)
|
2024-10-03 10:39:59 -05:00
|
|
|
if monthly_fee:
|
2024-10-03 17:08:59 -05:00
|
|
|
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)
|
2024-10-03 10:39:59 -05:00
|
|
|
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:
|
2024-10-08 18:41:02 -05:00
|
|
|
print(f"\tInvalid monthly fee: {member.get('monthly_fee')}", file=sys.stderr)
|
2024-10-03 10:39:59 -05:00
|
|
|
monthly_fee = None
|
|
|
|
else:
|
|
|
|
print(f"\tMonthly fee: {monthly_fee}", file=sys.stderr)
|
|
|
|
try:
|
|
|
|
yearly_fee = decimal.Decimal(member['yearly_fee'])
|
|
|
|
except:
|
2024-10-08 18:41:02 -05:00
|
|
|
print(f'\tInvalid yearly fee: {member.get("yearly_fee")}', file=sys.stderr)
|
2024-10-03 10:39:59 -05:00
|
|
|
yearly_fee = None
|
|
|
|
else:
|
|
|
|
print(f'\tYearly fee: {yearly_fee}', file=sys.stderr)
|
2024-10-03 17:08:59 -05:00
|
|
|
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)
|
2024-10-03 10:39:59 -05:00
|
|
|
if not args.dry_run:
|
|
|
|
invoice.save()
|
|
|
|
print(f'\tInvoice: {invoice.id} {invoice.description}', file=sys.stderr)
|
|
|
|
for line in invoice.lines:
|
2024-10-03 17:08:59 -05:00
|
|
|
print(f'\t\tLine: {line.description} {line.unit_price}', file=sys.stderr)
|
2024-10-04 06:57:49 -05:00
|
|
|
print(f'\tTotal amount: {invoice.total_amount}', file=sys.stderr)
|
2024-10-03 17:08:59 -05:00
|
|
|
member['invoice_id'] = invoice.id
|
|
|
|
csvwriter.writerow(member)
|