Files
einvoice/jr_einvoice/quick_cli.py
T

104 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Friendly CLI: einvoice --create --Customer ... --Entry ... --Volume ... --Price ..."""
import argparse
from datetime import date
import json
from pathlib import Path
import sys
from .create import create_invoice, decimal_input, find_customer, save_customer
from .logging_utils import setup_logging
from .paths import private_root, presets_root, resource_root
def parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description='Mit einem Aufruf eine validierte ZUGFeRD-Rechnung als PDF erzeugen.',
allow_abbrev=False)
mode = p.add_mutually_exclusive_group(required=True)
mode.add_argument('--create', action='store_true', help='Rechnung anlegen, PDF erzeugen und validieren')
mode.add_argument('--customers', action='store_true', help='Gespeicherte Kunden anzeigen')
p.add_argument('--Customer', '--customer', dest='customer', help='Gespeicherter Kundenname oder neuer Kunde')
p.add_argument('--Entry', '--entry', dest='entry', help='Leistungsbeschreibung')
p.add_argument('--Volume', '--volume', dest='volume', type=decimal_input, help='Menge, z. B. 120 oder 72,5')
p.add_argument('--Price', '--price', dest='price', type=decimal_input, help='Nettopreis je Einheit, z. B. "85,00 €"')
p.add_argument('--Unit', '--unit', dest='unit', default='HUR', help='Einheit: HUR=Stunden (Standard), C62=Stück')
p.add_argument('--Tax', '--tax', dest='tax', type=decimal_input, default=decimal_input('19'), help='USt in Prozent, Standard 19')
p.add_argument('--Number', '--number', dest='number', help='Freie Nummer; Standard automatisch JR-JJJJ-NNNN')
p.add_argument('--Date', '--date', dest='issued', type=date.fromisoformat, help='Rechnungsdatum YYYY-MM-DD, Standard heute')
p.add_argument('--Month', '--month', dest='month', help='Leistungsmonat YYYY-MM; auch aus deutschem Monatsnamen in Entry erkannt')
p.add_argument('--ServiceDate', '--service-date', dest='service_date', type=date.fromisoformat, help='Einzelnes Leistungsdatum YYYY-MM-DD')
p.add_argument('--DueDays', '--due-days', dest='due_days', type=int, default=30, help='Zahlungsziel in Tagen, Standard 30')
p.add_argument('--BuyerReference', '--buyer-reference', dest='buyer_reference', help='Kundenreferenz')
p.add_argument('--OrderReference', '--order-reference', dest='order_reference', help='Bestellnummer')
p.add_argument('--Street', '--street', dest='street', help='Straße/Hausnummer bei neuem Kunden')
p.add_argument('--PostalCode', '--postal-code', dest='postal_code', help='Postleitzahl bei neuem Kunden')
p.add_argument('--City', '--city', dest='city', help='Ort bei neuem Kunden')
p.add_argument('--Country', '--country', dest='country', default='DE', help='Ländercode, Standard DE')
p.add_argument('--Email', '--email', dest='email', help='Rechnungsadresse bei neuem Kunden (optional)')
p.add_argument('--VatId', '--vat-id', dest='vat_id', help='USt-ID bei neuem Kunden (optional)')
p.add_argument('--root', type=Path, default=private_root() / 'invoices', help='Stammordner für Rechnungen')
p.add_argument('--mustang-jar', type=Path, help='Abweichender Mustang-CLI-Pfad')
p.add_argument('--json', action='store_true', help='Ergebnisse als JSON; keine interaktiven Fragen')
p.add_argument('--non-interactive', action='store_true', help='Fehlende Daten als Fehler melden statt fragen')
return p
def main(argv=None):
p = parser()
args = p.parse_args(argv)
presets = presets_root()
try:
if args.customers:
for path in sorted((presets / 'customers').glob('*.yaml')):
from .presets import load_yaml
print(f'{path.stem}: {load_yaml(path).get("buyer", {}).get("name", "?")}')
return
missing = [flag for flag in ('customer', 'entry', 'volume', 'price') if getattr(args, flag) is None]
if missing:
p.error('Für --create fehlen: ' + ', '.join('--' + name.title() for name in missing))
if args.volume <= 0 or args.price < 0 or not (0 < args.tax <= 100) or args.due_days < 0:
p.error('Menge muss positiv sein; Preis/Zahlungsziel nicht negativ; Steuer > 0 und <= 100')
if find_customer(presets, args.customer) is None:
fields = {'street': args.street, 'postal_code': args.postal_code, 'city': args.city}
labels = {'street': 'Straße und Hausnummer', 'postal_code': 'Postleitzahl', 'city': 'Ort'}
absent = [name for name, value in fields.items() if not value]
if absent and (args.non_interactive or args.json or not sys.stdin.isatty()):
raise ValueError('Neuer Kunde: bitte ' + ', '.join('--' + {'street':'Street','postal_code':'PostalCode','city':'City'}[name] for name in absent) + ' angeben')
for name in absent:
fields[name] = input(f'Neuer Kunde {args.customer} – {labels[name]}: ').strip()
save_customer(presets, args.customer, {'name': args.customer, 'email': args.email,
'vat_id': args.vat_id, 'address': {**fields, 'country': args.country.upper()}})
if not args.json:
print(f'Kunde gespeichert: {args.customer}', flush=True)
else:
if any([args.street, args.postal_code, args.city, args.email, args.vat_id]) or args.country != 'DE':
raise ValueError('Kunde bereits gespeichert. Adressoptionen gelten nur bei Neuanlage; Stammdaten separat bearbeiten')
from .cli import _default_mustang_jar
result = create_invoice(root=args.root, resources=resource_root(), presets=presets,
customer=args.customer, entry=args.entry, volume=args.volume,
price=args.price, jar=args.mustang_jar or _default_mustang_jar(),
logger=setup_logging('WARNING', None), number=args.number,
issued=args.issued, month=args.month, service_date=args.service_date,
unit=args.unit, tax=args.tax, due_days=args.due_days,
buyer_reference=args.buyer_reference, order_reference=args.order_reference,
progress=(lambda message: None) if args.json else lambda message: print(message, flush=True))
if args.json:
print(json.dumps({'invoice_number': result.invoice.meta.invoice_number,
'invoice_dir': str(result.build.invoice_dir), 'pdf': str(result.attachment),
'validation_report': str(result.build.validate_report),
'log': str(result.build.out_dir / 'logs/run.log'),
'technical_validation': 'valid'}, ensure_ascii=False))
else:
inv = result.invoice
print(f'Leistung: {inv.meta.service_period_start or inv.meta.service_date}' +
(f' bis {inv.meta.service_period_end}' if inv.meta.service_period_end else ''))
print(f'PDF zum Anhängen an deine E-Mail:\n{result.attachment}')
print(f'Prüfbericht: {result.build.validate_report}')
print('Vor Versand Leistungsdaten, Betrag und Empfänger prüfen.')
except Exception as exc:
print(f'FEHLER: {exc}', file=sys.stderr)
raise SystemExit(1) from exc
if __name__ == '__main__':
main()