rufen method

Future<Map<String, dynamic>> rufen(
  1. String name, {
  2. Map<String, dynamic> params = const {},
  3. Duration? frist,
})

Einen Endpunkt rufen. params kommt zur Kasse dazu; null-Werte fallen weg, damit das Backend „nicht gesetzt" nicht als ausdrückliche Angabe missversteht.

frist überschreibt die Vorgabe für diesen einen Aufruf — der Abschluss eines Belegs darf länger warten als eine Belegliste.

Implementation

Future<Map<String, dynamic>> rufen(
  String name, {
  Map<String, dynamic> params = const {},
  Duration? frist,
}) async {
  // Beides frisch — siehe Klassenkommentar.
  final token = await idToken();
  final sitzung = await sessionId();
  if (token == null || token.isEmpty) {
    throw KasseneckValidationError(name, 'idToken lieferte kein Token', 'request');
  }
  if (sitzung == null || sitzung.isEmpty) {
    throw KasseneckValidationError(name, 'sessionId lieferte keine Sitzung', 'request');
  }

  final nutzlast = <String, dynamic>{'cashregisterId': cashregisterId};
  params.forEach((schluessel, wert) {
    if (wert != null) nutzlast[schluessel] = wert;
  });

  // Ausserhalb des try: ein nicht serialisierbarer Parameter ist ein
  // Programmierfehler und keine Netzstoerung. Im try darunter haette ihn der
  // Sammelfang als `network` gemeldet — ein Fehler, der nie am Netz lag,
  // saehe aus wie einer, nach dem ein Beleg entstanden sein koennte.
  final String rumpf = jsonEncode({'params': nutzlast});

  final http.Response antwort;
  try {
    antwort = await _http
        .post(
          Uri.parse('$baseUrl/$name'),
          headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer $token',
            'register-session': sitzung,
          },
          body: rumpf,
        )
        .timeout(frist ?? _timeout);
  } on TimeoutException catch (e) {
    // Getrennt vom Netzfehler: die Anfrage war draussen, der Ausgang ist
    // unbekannt — ueber `createReceipt` kann der Beleg laengst signiert
    // sein. Beides in denselben Ausgang zu werfen hiess, dem Aufrufer die
    // einzige Handhabe zu nehmen, die er hat.
    throw KasseneckHttpError(name, 0, KasseneckHttpError.zeitablauf, causeType: '${e.runtimeType}');
  } on Object catch (e) {
    // Nur der Typ, nie die Meldung: die kann eine Adresse tragen. Das Token
    // faehrt in der Kopfzeile und ist davon nicht betroffen.
    throw KasseneckHttpError(name, 0, KasseneckHttpError.netz, causeType: '${e.runtimeType}');
  }

  Object? roh;
  try {
    roh = jsonDecode(antwort.body);
  } on FormatException {
    throw KasseneckHttpError(name, antwort.statusCode, 'not-json');
  }
  if (roh is! Map) throw KasseneckHttpError(name, antwort.statusCode, 'missing-status');
  final huelle = Map<String, dynamic>.from(roh);
  if (huelle['status'] == 'success') {
    final daten = huelle['data'];
    // Fehlendes `data` ist erlaubt — nicht jeder Aufruf hat eine Nutzlast.
    // Ein `data`, das da ist und **kein Objekt** ist (Array, Zahl, Text), ist
    // dagegen kaputt und darf nicht als leeres Objekt durchgehen: aus dem
    // wurde weiter oben ein voller Standardsatz Einstellungen, und der
    // Bildschirm meldete „der Betrieb hat nichts eingestellt". Dieselbe
    // Grenze wie bei den Listen: leer ist etwas anderes als kaputt.
    if (daten == null) return <String, dynamic>{};
    if (daten is! Map) {
      throw KasseneckHttpError(name, antwort.statusCode, 'data-not-object');
    }
    return Map<String, dynamic>.from(daten);
  }
  final meldung = huelle['message'];
  throw KasseneckApiError(name, meldung is String && meldung.isNotEmpty ? meldung : 'Der Aufruf ist fehlgeschlagen.',
      code: fehlercodeAus(huelle));
}