/** * Typen der Rechnungs-API. Die Anfragen folgen `vertrag.ts` Feld fuer Feld; * `test/rechnung-client.test.ts` schickt die Beispiele des Vertrags durch. * * Betraege sind ueberall ganze Cent. Datumsangaben sind `YYYY-MM-DD` nach * Wiener Kalender. */ import type { CreditNoteReason, CustomerType, DocType, EInvoiceFormat, EInvoiceMissingCode, InvoiceLanguage, InvoiceNoticeCode, ItemKind, ReverseChargeReason, InvoicePaymentMethod, InvoiceUnit, InvoiceSetupRequirement, InvoiceListStatus, PriceMode, ReturnDisposition, TaxScheme, VatRatePercent, WriteOffReasonCode } from './vertrag.js'; export interface CustomerInput { type: CustomerType; name: string; legalForm?: string; email?: string; phone?: string; street?: string; houseNumber?: string; zip?: string; city?: string; /** ISO-3166-Alpha-2, z. B. `AT`. */ country: string; /** UID-Nummer, z. B. `ATU12345675`. */ vatId?: string; /** Kuerzel fuer die Rechnungsnummer; einmal gesetzt unveraenderlich. */ shortCode?: string; /** Behoerde (B2G) — die E-Rechnung verlangt dann Adresse und `orderReference`. */ isAuthority?: boolean; note?: string; /** Kennung im eigenen System; je Konto eindeutig. */ externalId?: string; /** Sprache der Rechnungen an diesen Kunden; fehlt = `de`. Behoerden immer `de`. */ language?: InvoiceLanguage; } export interface Customer { id: string; type: CustomerType; name: string; legalForm: string | null; email: string | null; phone: string | null; street: string | null; houseNumber: string | null; zip: string | null; city: string | null; country: string; vatId: string | null; shortCode: string | null; isAuthority: boolean; note: string | null; externalId: string | null; language: InvoiceLanguage; createdAt: string | null; updatedAt: string | null; } export interface CustomerSearch { externalId?: string; vatId?: string; email?: string; /** Namensanfang, Gross-/Kleinschreibung und Akzente egal. */ name?: string; limit?: number; cursor?: string; } export interface CustomerPage { customers: Customer[]; nextCursor: string | null; } export interface InvoiceItemInput { description: string; subtitle?: string; /** Hoechstens drei Nachkommastellen. */ quantity: number; /** Einheit aus `INVOICE_UNITS`; ohne Angabe `piece`. */ unit?: InvoiceUnit; /** Ware oder Leistung; ohne Angabe `goods`. Entscheidet ueber den Steuerfall. */ kind?: ItemKind; /** * Einzelpreis in ganzen Cent, im `priceMode` der Rechnung (netto oder * brutto). Genau eines von `unitPriceCents` und `unitPriceMicros`. */ unitPriceCents?: number; /** * Einzelpreis in Mikro-Euro (10⁻⁶ €), fuer Preise unterhalb eines Cents. * Genau eines von `unitPriceCents` und `unitPriceMicros`. */ unitPriceMicros?: number; vatRate: VatRatePercent; /** Zeilenrabatt in Prozent, hoechstens zwei Nachkommastellen. */ discountPct?: number; /** Artikel aus dem Artikelstamm; bestandsgefuehrt bucht das Ausstellen ihn vom Lager ab. */ articleId?: string; } /** * Eine Position beim Ausstellen (`issueInvoice`): dazu optional die * Reservierung aus der Lager-API, die diese Position einloest. */ export interface IssueInvoiceItemInput extends InvoiceItemInput { /** * Kennung aus `createReservation` (`./inventory`); braucht `articleId`. * Geprueft beim Ausstellen (`reservation_not_found`, `reservation_mismatch`, * `reservation_not_active`; abgelaufen = Hinweis `reservation_expired`), * eingeloest beim Buchen der Rechnung am Lagerstandort der Rechnung. */ reservationId?: string; } /** Eine Gutschriftsposition: wie eine Rechnungsposition (ohne Reservierung), dazu die Rueckgabe-Wahl. */ export interface CreditNoteItemInput extends InvoiceItemInput { /** Wohin die Ware dieser Position geht; fehlt = Vorgabe des Aufrufs bzw. `restock`. */ returnDisposition?: ReturnDisposition; } export interface IssueInvoiceRequest { /** Pflicht: dieselbe Anfrage mit demselben Schluessel erzeugt nie eine zweite Rechnung. */ idempotencyKey: string; /** Pflicht ueber 400 € brutto sowie bei Reverse Charge und ig. Lieferung. */ customerId?: string; /** Optional: der Server leitet den Fall ab und prueft eine Angabe dagegen. */ taxScheme?: TaxScheme; /** Pflicht bei `domesticReverseCharge`. */ reverseChargeReason?: ReverseChargeReason; priceMode: PriceMode; /** Leistungsdatum bzw. Beginn des Leistungszeitraums. */ serviceStart: string; serviceEnd?: string; paymentTermDays?: number; orderReference?: string; intro?: string; note?: string; /** Verwendungszweck fuer die Ueberweisung; leer = Rechnungsnummer. */ paymentReference?: string; girocode?: boolean; tracking?: boolean; items: IssueInvoiceItemInput[]; /** Eigene Merkmale (hoechstens 20), nie gedruckt. */ metadata?: Record; /** Sprache dieser Rechnung; sonst die des Kunden, sonst `de`. Eine Rechnung, eine Nummer, eine Sprache. */ language?: InvoiceLanguage; /** Marke (Kennung aus `listBrands`); sonst die Standardmarke. Unbekannt = `brand_not_found`. */ brandId?: string; /** Lager-Standort fuer bestandsgefuehrte Positionen; sonst der Standard-Standort. */ stockLocationId?: string; /** * Schon bezahlt (Shop kassiert online, Rechnung folgt). Die Zahlung entsteht * in derselben Transaktion wie das Festschreiben: das PDF traegt dann keine * Zahlungsinformationen und keinen Giro-QR. */ payment?: PaymentInput; /** * Nur fuer Rechnungen ohne Steuer, die die UID des Kunden verlangen (ig. * Lieferung, Reverse Charge): hat die UID-Pruefung noch kein Ergebnis * (`vat_id_check_pending`), stellt `true` trotzdem aus. Der Aussteller traegt * dann das Risiko, die Rechnung traegt `vatIdRisk`. Eine ungueltige UID * (`vat_id_invalid`) sperrt auch damit. Gehoert nicht zur Anfrage im Sinn der * Idempotenz: dieselbe Anfrage mit und ohne das Feld ist dieselbe Rechnung. */ acceptVatIdRisk?: boolean; } /** Eine Zahlung, wie das Fremdsystem sie meldet. */ export interface PaymentInput { method: InvoicePaymentMethod; /** Ohne Angabe der volle Bruttobetrag. */ amountCents?: number; /** Ohne Angabe der heutige Wiener Tag; nie in der Zukunft. */ paidAt?: string; /** Zahlungskennung des Fremdsystems — gespeichert, aber nicht gedruckt. */ reference?: string; /** * Die Zahlung erfolgte vor Ort beim Unternehmer (Terminal an der Kasse). * Dann ist sie ein Barumsatz — auch mit Karte (§ 131b Abs. 1 Z 3 BAO) — und * die Antwort traegt den Hinweis `cash_receipt_required`. Zu `transfer` * passt das Kennzeichen nicht und wird abgewiesen. */ onSite?: boolean; } export interface RecordPaymentRequest extends PaymentInput { /** Pflicht: ohne ihn bucht eine Wiederholung eine zweite Zahlung. */ idempotencyKey: string; invoiceId: string; } /** Eine gebuchte Zahlung, wie die API sie zurueckgibt. */ export interface InvoicePayment { id: string; amountCents: number; paidAt: string; method: InvoicePaymentMethod; reference: string | null; } /** * Ein Hinweis zu einer erfolgreichen Antwort — kein Fehler, sondern etwas, * das der Aufrufer wissen sollte. Die Codes stehen in `INVOICE_NOTICE_CODES`. */ export interface InvoiceNotice { code: InvoiceNoticeCode; message: string; /** Bei `reservation_expired`: die abgelaufene Reservierung. */ reservationId?: string; } export interface RecordPaymentResult { invoice: Invoice; payment: InvoicePayment; replayed: boolean; /** * Hinweise zu dieser Zahlung — eine **Liste** wie bei `issueInvoice` * (bis 0.21.0 ein einzelnes Objekt). Fehlt, wenn es keinen gibt. */ notice?: InvoiceNotice[]; } export interface CancelInvoiceRequest { idempotencyKey: string; invoiceId: string; reason: CreditNoteReason; note?: string; /** Wohin die Ware bestandsgefuehrter Positionen geht; fehlt = `restock`. */ returnDisposition?: ReturnDisposition; } export interface CreditNoteRequest extends CancelInvoiceRequest { items: CreditNoteItemInput[]; } /** * Summen einer Rechnung in Cent, **immer positiv** — auch bei einer Gutschrift * (`docType: 'credit_note'`); das Vorzeichen steht im Belegtyp, nicht im Betrag. * Gerechnet wird je Satz wie in [computeInvoiceTotals]. */ export interface InvoiceTotals { netCents: number; vatCents: number; grossCents: number; /** Je USt-Satz, absteigend. `grossCents` ist `netCents + vatCents` (seit 0.22.0 mitgeliefert). */ byRate: InvoiceRateTotals[]; } export interface InvoiceRateTotals { rate: number; netCents: number; vatCents: number; grossCents: number; } export interface EInvoiceStatus { level: string; formats: string[]; /** Was einer E-Rechnung fehlt, als Code aus `EINVOICE_MISSING_CODES`. */ missing: EInvoiceMissingCode[]; } export interface Invoice { id: string; number: string; docType: DocType; status: string; invoiceDate: string; dueDate: string | null; customerId: string | null; totals: InvoiceTotals; einvoice: EInvoiceStatus | null; statusUrl: string | null; statusPassword: string | null; metadata: Record; /** Beim Festschreiben eingefroren; aeltere Rechnungen `de`. */ language: InvoiceLanguage; /** Die eingefrorene Marke; `id` ist `null` bei der Ersatzmarke ohne eigene Einrichtung. */ brand: { id: string | null; name: string | null; } | null; /** Summe der gebuchten Zahlungen. */ paidCents: number; /** Was noch offen ist; `0` heisst bezahlt. */ openCents: number; /** Beim Festschreiben eingefrorener UID-Nachweis des Kunden; sonst `null`. */ vatIdProof: InvoiceVatIdProof | null; /** Ohne Ergebnis der UID-Pruefung mit `acceptVatIdRisk` ausgestellt; sonst `null`. */ vatIdRisk: InvoiceVatIdRisk | null; } /** UID-Nachweis an einer Rechnung: gueltige Pruefung der Kunden-UID, hoechstens 30 Tage vor dem Festschreiben. */ export interface InvoiceVatIdProof { /** Wiener Tag der Pruefung (`YYYY-MM-DD`). */ checkedOn: string; /** Wer geprueft hat: FinanzOnline (Stufe 2 mit Name und Anschrift) oder VIES. */ source: 'finanzonline' | 'vies'; /** Stufe der Pruefung: 1 = nur UID, 2 = UID mit Name und Anschrift. */ level: 1 | 2; /** Pruefcode fuer die oeffentliche Pruefseite (auf dem PDF als QR); `null`, wenn keiner vergeben wurde. */ code: string | null; /** `true`, wenn erst nach dem Leistungstag geprueft wurde (kein Nachweis am oder vor dem Leistungstag). */ afterSupplyDate: boolean; /** Pruefung am oder vor dem Leistungstag (ohne Pruefcode); `null`, wenn es keine gibt. */ supplyDateProof: InvoiceSupplyDateProof | null; } /** Nachweis am oder vor dem Leistungstag: wann, wer, welche Stufe. */ export interface InvoiceSupplyDateProof { /** Wiener Tag der Pruefung (`YYYY-MM-DD`). */ checkedOn: string; source: 'finanzonline' | 'vies'; /** Stufe der Pruefung: 1 = nur UID, 2 = UID mit Name und Anschrift. */ level: 1 | 2; } /** Ausgestellt ohne Ergebnis der UID-Pruefung, das Risiko traegt der Aussteller. */ export interface InvoiceVatIdRisk { /** Wiener Tag der Bestaetigung (`YYYY-MM-DD`). */ acceptedOn: string; } /** Eine Marke des Kontos (Logo, Farbe, Absender) — `id` geht als `brandId` in `issueInvoice`. */ export interface Brand { id: string; name: string; isDefault: boolean; } export interface InvoiceRecipient { name: string; type: CustomerType; street: string | null; houseNumber: string | null; zip: string | null; city: string | null; country: string; vatId: string | null; shortCode: string | null; email: string | null; isAuthority: boolean; } /** Eine gespeicherte Position — wie gesendet, fehlende Angaben mit ihrem Standardwert. */ export interface InvoiceItem { description: string; subtitle: string; quantity: number; unit: string; /** Ware oder Leistung — Altbestand ohne Angabe zaehlt als `goods`. */ kind: ItemKind; /** * Einzelpreis in ganzen Cent — eine ANZEIGEHILFE: kaufmaennisch aus * `unitPriceMicros` gerundet, und damit 0, sobald der Preis unter einem * halben Cent liegt. Verbindlich sind `unitPriceMicros` und die Betraege je * Position (§ 9.3). */ unitPriceCents: number; /** Einzelpreis in Mikro-Euro (10⁻⁶ €) — der verbindliche Preis. */ unitPriceMicros?: number; vatRate: number; discountPct: number; } /** * Eine gebuchte Zahlung in `getInvoice`. Bei Altbestand kann jede Angabe ausser * dem Betrag fehlen und kommt dann als `null`. Das Datum heisst hier `date` * (in `recordInvoicePayment.payment` dagegen `paidAt`), beides ist Draht. */ export interface InvoiceDetailPayment { id: string | null; amountCents: number; date: string | null; method: InvoicePaymentMethod | null; /** Zahlungskennung des Fremdsystems, wie bei `recordInvoicePayment` gesetzt. */ reference: string | null; } export interface InvoiceDetail extends Invoice { items: InvoiceItem[]; customer: InvoiceRecipient | null; taxScheme: TaxScheme; /** Nur bei `domesticReverseCharge`. */ reverseChargeReason: ReverseChargeReason | null; /** Land, dessen Steuer die Rechnung traegt; Altbestand `AT`. */ taxCountry: string; priceMode: PriceMode; serviceStart: string | null; serviceEnd: string | null; paymentTermDays: number | null; orderReference: string | null; /** Gebuchte Zahlungen. Heisst das Datum hier `date`, in `recordInvoicePayment` `paidAt`. */ payments: InvoiceDetailPayment[]; paidCents: number; openCents: number; overdue: boolean; writtenOff: boolean; writeOffReasonCode: WriteOffReasonCode | null; related: { invoiceId: string; number: string | null; } | null; creditNotes: { id: string; number: string; grossCents: number; }[]; source: string | null; createdAt: string | null; finalizedAt: string | null; } export interface InvoiceListQuery { from?: string; to?: string; status?: InvoiceListStatus; docType?: DocType; customerId?: string; limit?: number; cursor?: string; } export interface InvoicePage { invoices: Invoice[]; nextCursor: string | null; } export interface IssueResult { invoice: Invoice; /** `true`, wenn die Anfrage schon einmal ausgefuehrt wurde und die bestehende Rechnung zurueckkommt. */ replayed: boolean; /** * Hinweise zu dieser Rechnung — kein Fehler, sondern etwas, das der Aufrufer * wissen sollte. Eine **Liste**, weil mehrere zugleich anfallen koennen: eine * bar bezahlte ig. Lieferung traegt sowohl `cash_receipt_required` als auch * `recapitulative_statement_due`. */ notice?: InvoiceNotice[]; } /** * Was `issueInvoice` mit dieser Anfrage ausstellen wuerde — ohne Nummer, ohne * Dokument, ohne Zahlung. Der Probelauf prueft wie das Ausstellen (Einrichtung, * Kunde, Steuerfall, Pflichtangaben, Marke, Zahlung) und scheitert mit * denselben Fehlercodes. */ export interface InvoicePreview { docType: 'invoice'; /** Heutiger Wiener Tag — der Tag, den eine sofort ausgestellte Rechnung truege. */ invoiceDate: string; dueDate: string | null; customerId: string | null; /** Der abgeleitete Steuerfall. */ taxScheme: TaxScheme; /** Warum dieser Fall gilt (z. B. `customer_country_eu_with_vat_id`). */ taxSchemeReason: string; reverseChargeReason: ReverseChargeReason | null; taxCountry: string; priceMode: PriceMode; totals: InvoiceTotals; language: InvoiceLanguage; brand: { id: string | null; name: string | null; } | null; einvoice: EInvoiceStatus; } export interface PreviewResult { preview: InvoicePreview; /** Dieselben Hinweise, die das Ausstellen liefern wuerde. */ notice?: InvoiceNotice[]; } export interface CancelResult { creditNote: Invoice; original: { id: string; status: string; }; /** Bereits auf das Original bezahlt — die Rueckerstattung ist Sache des Betriebs. */ originalPaidCents: number; replayed: boolean; } export interface CreditNoteResult { creditNote: Invoice; /** Brutto, das nach dieser Gutschrift noch gutgeschrieben werden kann. */ remainingCents: number; replayed: boolean; } /** * Die E-Rechnung, wie der Server sie schickt. `filename` ist unter `/v3` * `invoice-.xml` und wird nie selbst gebaut: so heisst die Datei * ueberall gleich (Panel, Mail, API). */ export interface InvoiceXml { xml: string; format: EInvoiceFormat; filename: string; } /** Ein Punkt, der vor dem Ausstellen noch fehlt. */ export interface InvoiceSetupGap { requirement: InvoiceSetupRequirement; /** Was fehlt und wo es nachzutragen ist — fuer einen Menschen formuliert. */ message: string; } export interface InvoiceSetupStatus { /** `true`: Rechnungen koennen ueber die API ausgestellt werden. */ ready: boolean; /** Umgebung des Schluessels (`kr_live_…` bzw. `kr_test_…`). */ environment: 'live' | 'test'; /** Was fehlt, in der Reihenfolge von `INVOICE_SETUP_REQUIREMENTS` — leer, wenn `ready`. */ missing: InvoiceSetupGap[]; }