computeInvoiceTotals function

InvoiceTotals computeInvoiceTotals(
  1. Iterable<TotalsItem> items,
  2. String priceMode, [
  3. String taxScheme = 'normal'
])

Netto, USt und Brutto einer Rechnung in Cent, je Satz absteigend — dieselbe Form wie invoice.totals in den Antworten.

taxScheme ist der Steuerfall der Rechnung, ohne Angabe normal. Leitet der Server einen steuerfreien Fall ab (etwa eine ig. Lieferung), gehört dieser Fall hierher — sonst rechnet die Funktion Steuer, die die Rechnung nicht ausweist.

Wirft ArgumentError bei einem priceMode außerhalb von priceModes, einem taxScheme außerhalb von taxSchemes und bei einer Zeile, die keine endliche Zahl ergibt. Im JS-Paket schließt der Typ die ersten beiden aus; eine stille Rechnung im falschen Modus wäre hier ein falscher Betrag.

Implementation

InvoiceTotals computeInvoiceTotals(Iterable<TotalsItem> items, String priceMode, [String taxScheme = 'normal']) {
  if (!priceModes.contains(priceMode)) {
    throw ArgumentError.value(priceMode, 'priceMode', 'erwartet: ${priceModes.join(', ')}');
  }
  if (!taxSchemes.contains(taxScheme)) {
    throw ArgumentError.value(taxScheme, 'taxScheme', 'erwartet: ${taxSchemes.join(', ')}');
  }
  final steuerfrei = zeroRatedTaxSchemes.contains(taxScheme);
  final bruttoPreise = priceMode == 'gross' && !steuerfrei;

  // Ungerundete Zeilen je Satz, in Euro wie am Server. Die Reihenfolge der
  // Rechenschritte ist die des JS-Zwillings — nur dann trifft das Gleitkomma
  // Bit fuer Bit dieselbe Zahl, und nur dann rundet ein Grenzfall gleich.
  // num statt int als Schluessel: es gibt Saetze mit Nachkommastelle (4,9 %
  // Grundnahrungsmittel). Zwei Zeilen zu 4,9 % landen im selben Eintrag, weil
  // gleiche Zahlen in Dart gleich hashen -- auch 20 und 20.0.
  final jeSatz = <num, double>{};
  for (final p in items) {
    final satz = steuerfrei ? 0 : p.vatRate;
    // `priceInCents` nimmt den Preis aus dem Feld, das ihn traegt (Cent oder
    // Mikro-Euro). Die Rechenschritte bleiben dieselben wie im JS-Zwilling:
    // nur so trifft das Gleitkomma Bit fuer Bit dieselbe Zahl. Exakt gerundet
    // wird erst mit dem Stichtag (Schalter), im Zwilling ebenso.
    final zeile = (p.priceInCents / 100) * p.quantity * (1 - (p.discountPct ?? 0) / 100);
    if (!zeile.isFinite) {
      throw ArgumentError('Position ergibt keine endliche Zahl (Menge, Preis oder Rabatt)');
    }
    jeSatz[satz] = (jeSatz[satz] ?? 0.0) + zeile;
  }

  final byRate = <VatRateTotal>[];
  for (final MapEntry(key: rate, value: roh) in jeSatz.entries) {
    final int netCents;
    final int vatCents;
    if (bruttoPreise) {
      final grossCents = _euroZuCent(roh);
      netCents = _centRund(grossCents * 100 / (100 + rate));
      vatCents = grossCents - netCents;
    } else {
      netCents = _euroZuCent(roh);
      vatCents = _euroZuCent(roh * rate / 100);
    }
    byRate.add(VatRateTotal(rate: rate, netCents: netCents, vatCents: vatCents, grossCents: netCents + vatCents));
  }
  byRate.sort((a, b) => b.rate.compareTo(a.rate));

  var net = 0;
  var vat = 0;
  var gross = 0;
  for (final r in byRate) {
    net += r.netCents;
    vat += r.vatCents;
    gross += r.grossCents;
  }
  return InvoiceTotals(netCents: net, vatCents: vat, grossCents: gross, byRate: List.unmodifiable(byRate));
}