Beispielcode
Pseudocode mit dem Python-Client weclappy, nicht ausgeführt und nur für einen Testmandanten gedacht. Er nutzt ausschließlich dokumentierte Endpunkte; die Postman Collection für die weclapp API enthält sie zum Ausprobieren.
import time
from weclappy import WeclappAPIError # Timeout/Verbindungsabbruch: Ausgang unbekannt (ebenfalls WeclappAPIError)
WAIT = (0, 2, 5, 10, 30) # Sekunden: Queue und Verarbeitung abwarten, bevor neu angelegt wird
def find_invoice(client, key, waits=(0,)):
for pause in waits:
time.sleep(pause)
rows = client.get("salesInvoice", params={
"commission-eq": key, "properties": "id,version,status,commission,salesInvoiceType"})
if any(r.commission != key for r in rows): # 1. Filter still ignoriert (falscher Operator): Lauf anhalten
raise RuntimeError(f"Filter ignoriert, nichts anlegen: {key}")
hits = [r for r in rows if r.status != "CANCELLED" and r.salesInvoiceType != "CREDIT_NOTE"] # 2. Storno und Gutschrift zählen nicht
if hits:
return hits
return []
def finish(client, inv):
if inv.status == "OPEN_ITEM_CREATED": # schon abgeschlossen
return inv
return client.put(f"salesInvoice/id/{inv.id}", {"status": "OPEN_ITEM_CREATED", "version": inv.version},
params={"ignoreMissingProperties": "true"}) # ohne den Parameter HTTP 400; keine salesInvoiceItems mitsenden; bei 409 neu lesen
def bill_one(client, sub, run_id, customers):
key = f"{run_id}-{sub.contract_no}" # Lauf-SchlĂĽssel
customer_id = customers.get(sub.customer_no) or create_customer(client, sub, customers)
hits = find_invoice(client, key)
if len(hits) > 1:
return record_error(sub, "mehrfach", key) # PrĂĽfliste, nicht raten
if hits:
return finish(client, hits[0]) # existiert schon: nur noch abschlieĂźen
try:
inv = client.post("salesInvoice", {
"customerId": customer_id, "commission": key,
"invoiceDate": sub.invoice_date_ms, # Berlin-Mitternacht
"servicePeriodFrom": sub.from_ms, "servicePeriodTo": sub.to_ms,
"salesInvoiceItems": [{
"title": sub.plan, "quantity": "1",
"unitPrice": sub.net_price, # Dezimalstring, netto, Mandantenwährung
"manualUnitPrice": True, # Preis nicht aus Preisliste (laut Feldname)
"taxId": tax_id_for(sub), # z. B. GET /tax/findSalesTax, mit Steuerberatung abstimmen
"servicePeriodFrom": sub.from_ms, "servicePeriodTo": sub.to_ms}]})
return finish(client, inv) # Abschluss per PUT; ein POST allein endet in NEW
except WeclappAPIError as exc: # Transportfehler und Timeouts gesondert behandeln: nicht wiederholen, erst suchen
if exc.is_validation_error: # HTTP 400 ohne request_timeout: kann nicht angekommen sein
return record_error(sub, type(exc).__name__, exc.detail)
found = find_invoice(client, key, waits=WAIT) # 3. nach Fehler erst Wartefolge, dann Suche
if found:
return finish(client, found[0]) # Rechnung ist angekommen: nur noch abschlieĂźen
record_error(sub, type(exc).__name__, exc.detail) # unklar: Prüfliste, im nächsten Pass wieder mit der Suche beginnen