# Reinigung

> Der Reinigungsplan, die tatsächlich erledigten Aufgaben und die Fotos, die es belegen: drei Collections, und was ein fehlender Datensatz nicht beweist.

Source: https://developers.backresto.com/de/docs/cleaning

Reinigung ist in der App ein Plan und sein Beleg. Das Restaurant schreibt auf,
was gereinigt werden muss, wo und wie oft; die Mitarbeiter haken die Aufgaben
ab, während sie sie erledigen; manche dieser Aufgaben verlangen ein Foto. Drei
Collections, eine pro Ebene, und die Verbindung zwischen ihnen ist der Teil mit
der meisten Arbeit.

| Collection | Scope | Was ein Datensatz ist |
| --- | --- | --- |
| `cleaning-tasks` | `cleaning-tasks:read` | Eine Zeile des Plans: was gereinigt werden muss, wo, wie oft, ob es ein Foto braucht. |
| `cleaning-task-records` | `cleaning-task-records:read` | Eine tatsächlich erledigte Aufgabe: wann und von wem. |
| `cleaning-task-pictures` | `cleaning-task-pictures:read` | Das Foto, das es belegt. Trägt eine Datei. |

Alle drei teilen sich den [Snapshot-Umschlag](https://developers.backresto.com/de/docs/collections): `id`,
`deleted`, `capturedAt`, `receivedAt`, `sequence` und der Rest stehen neben dem
`data`, das unten beschrieben ist.

## `cleaning-tasks`

Der Plan selbst. Er ändert sich selten: Ein Restaurant schreibt seinen Plan
einmal und bearbeitet ihn, wenn sich die Küche ändert. Das ist also die
Collection, die Sie am seltensten durchlaufen und am längsten zwischenspeichern.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `index` | integer, **Pflicht** | Die Position der Aufgabe im Plan, so wie die App sie ordnet. |
| `name` | string, **Pflicht** | Was gereinigt werden muss, in den Worten des Restaurants. |
| `description` | string \| null | Längere Anweisungen, sofern jemand welche geschrieben hat. |
| `areaId` | string \| null | Der [Bereich](https://developers.backresto.com/de/docs/facilities), zu dem sie gehört. `null` bei einem Restaurant, das sich nicht in Bereiche aufteilt. |
| `recurrence` | integer, **Pflicht** | Wie oft die Aufgabe wiederkehrt, als schlichte ganze Zahl. |
| `requirePhotoProof` | boolean \| null | `true`, wenn die Aufgabe ein Foto verlangt. `null` heißt, dass die App es nie gesetzt hat. |
| `isDeleted` | boolean, **Pflicht** | Das eigene Soft-Delete-Flag der App. Lesen Sie die Anmerkung unten. |

```json
{
  "id": "c9a4f7e2-1b83-4d05-a6f1-90e3b7c4d218",
  "collection": "cleaning-tasks",
  "deleted": false,
  "capturedAt": "1789601204773",
  "receivedAt": "1789601399042",
  "sequence": "1",
  "data": {
    "index": 3,
    "name": "Nettoyage de la trancheuse",
    "description": "Démonter la lame, dégraisser, désinfecter.",
    "areaId": "7d2c0b61-4e8a-49f3-9c27-5a1b8e60d3f4",
    "recurrence": 1,
    "requirePhotoProof": true,
    "isDeleted": false
  }
}
```

**`recurrence` trägt keine Einheit.** Es ist eine ganze Zahl, und der Datensatz
sagt nicht, was sie zählt. Zeigen Sie sie so an, wie die App sie anzeigt, statt
aus einer Vermutung „alle 1 Tage“ zu machen.

**`isDeleted` ist nicht das `deleted` des Umschlags.** Eine Aufgabe, die das
Restaurant aus seinem Plan entfernt hat, kommt mit `isDeleted: true` und
`deleted: false` zurück: Sie ist weiterhin ein lebender Datensatz und beschreibt
eine Zeile, die nicht mehr gilt. Filtern Sie auf beides und behalten Sie die
gelöschten: Alte Datensätze zeigen weiterhin auf sie.

## `cleaning-task-records`

Eine erledigte Aufgabe. Absichtlich klein: welche Aufgabe, wann, wer.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Wann die Aufgabe erledigt wurde, als String mit Millisekunden seit Epoch. |
| `cleaningTaskId` | string, **Pflicht** | Der `cleaning-tasks`-Datensatz, den sie erfüllt. |
| `userId` | string \| null | Der [Mitarbeiter](https://developers.backresto.com/de/docs/facilities), der sie erledigt hat, sofern die App einen erfasst hat. |

```json
{
  "id": "5f8b3d16-2c90-4a77-b4e8-31d0a9c65f7b",
  "collection": "cleaning-task-records",
  "deleted": false,
  "capturedAt": "1789775012558",
  "receivedAt": "1789775204910",
  "sequence": "2",
  "data": {
    "timestamp": "1789774800000",
    "cleaningTaskId": "c9a4f7e2-1b83-4d05-a6f1-90e3b7c4d218",
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

`cleaningTaskId` ist Pflicht, jeder Datensatz hat also eine Aufgabe. Umgekehrt
gilt das nicht: Eine Aufgabe kann wochenlang ohne einen einzigen Datensatz im
Plan stehen, und das ist der normale Zustand einer wöchentlichen Aufgabe an
einem Dienstag.

## `cleaning-task-pictures`

Das Foto. Es trägt eine Datei, und damit ist es die eine Collection hier, die
Sie nicht allein über den Datensatz-Endpunkt nutzen können.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Wann das Foto aufgenommen wurde, als String mit Millisekunden seit Epoch. |
| `cleaningTaskRecordId` | string \| null | Der `cleaning-task-records`-Datensatz, den es belegt. Darf `null` sein. Siehe unten. |
| `part` | number \| null | Nicht dokumentiert, und eine Dezimalzahl statt eines Zählers. Führen Sie es unverändert mit, statt es zu deuten (derselbe Vorbehalt wie bei den [Rückverfolgbarkeitsetiketten](https://developers.backresto.com/de/docs/traceability-labels)). |
| `groupId` | string \| null | Bindet diese Fotos zusammen. Datensätze mit derselben `groupId` gehören zum selben Beleg. |
| `asset` | object, **Pflicht** | Benennt die Datei: `objectKey` und `status` immer, dazu `contentType`, `byteLength` und `sha256`, sobald sie hochgeladen ist. |

```json
{
  "id": "2d71c8a9-6e34-4b0f-8517-ac93e25d0b46",
  "collection": "cleaning-task-pictures",
  "deleted": false,
  "capturedAt": "1789775118330",
  "receivedAt": "1789775301774",
  "sequence": "3",
  "data": {
    "timestamp": "1789774860000",
    "cleaningTaskRecordId": "5f8b3d16-2c90-4a77-b4e8-31d0a9c65f7b",
    "part": 1,
    "groupId": "a03e5c88-71bd-4f92-b6d4-2e8f10c73a5b",
    "asset": {
      "objectKey": "cleaning-task-pictures/2d71c8a9/1.jpg",
      "status": "uploaded",
      "contentType": "image/jpeg",
      "byteLength": 812043,
      "sha256": "9f2a…"
    }
  }
}
```

Die Bytes sind ein zweiter Aufruf, gegen die eigene Kennung des Datensatzes:

```http
GET /v1/restaurants/{restaurantId}/collections/cleaning-task-pictures/records/{recordId}/assets
```

Er antwortet mit einer Liste signierter URLs, jede **fünfzehn Minuten** gültig,
jede mit ihrer eigenen Autorisierung, sodass der Download keinen Header braucht.
Holen Sie die Bytes jetzt, speichern Sie die Bytes statt des Links, und rufen
Sie den Endpunkt erneut auf, wenn Sie eine frische URL brauchen. Die Regeln
(was erscheint, wozu `sha256` da ist, warum es sich lohnt, `contentType` zu
lesen) stehen auf [der Collections-Seite](https://developers.backresto.com/de/docs/collections), und hier gelten
dieselben.

**`asset.status` sagt Ihnen, ob es Bytes zu holen gibt.** Es ist `pending` oder
`uploaded`. Ein Asset mit `pending` ist ein Foto, das das Gerät angekündigt und
noch nicht fertig gesendet hat: Der Datensatz ist echt, die Datei nicht, und der
Assets-Endpunkt antwortet ohne sie. Lesen Sie den Status, bevor Sie eine leere
Asset-Liste als ein Foto behandeln, das nie aufgenommen wurde.

**`cleaningTaskRecordId` darf `null` sein.** Ein Foto kann existieren, ohne auf
einen Datensatz zu zeigen; ein Join, der es immer erwartet, lässt Fotos unter
den Tisch fallen. Zählen Sie sie getrennt, statt sie stillschweigend zu
verwerfen.

## Die drei verbinden

Die Struktur, die Sie fast sicher wollen, ist eine Zeile pro erledigter Aufgabe,
mit ihrem Planeintrag und ihren Fotos:

- `cleaning-task-records.cleaningTaskId` → `cleaning-tasks.id`
- `cleaning-task-pictures.cleaningTaskRecordId` → `cleaning-task-records.id`

Drei Durchläufe, auf Ihrer Seite verbunden. Es gibt keinen Endpunkt, der das für
Sie tut, und es gibt keinen Filter nach Aufgabe oder nach Datum: Sie
durchlaufen jede Collection ganz und gleichen ab, so wie es
[die Seite zum Umschlag](https://developers.backresto.com/de/docs/collections) beschreibt.

Für Berichte zählt `capturedAt`, für den Abgleich `receivedAt`: Ein Tablet in
einem Kühlraum lädt hoch, sobald es Empfang findet, und ein Foto von 09:00 Uhr
kann um 14:00 Uhr ankommen. Das kann nach dem Datensatz sein, zu dem es gehört,
oder davor.

## Was ein fehlender Datensatz nicht heißt

**Ein Planeintrag ohne passenden Datensatz ist kein Beweis, dass die Aufgabe
ausgefallen ist.** Sie kann auf einem Gerät erledigt worden sein, das noch nicht
hochgeladen hat, oder erledigt worden sein, bevor die Erfassung für dieses
Restaurant eingeschaltet wurde. Dasselbe gilt für eine Aufgabe mit
`requirePhotoProof: true` ohne ein Foto dazu: Das Foto kann noch im Upload sein,
und ein Asset, dessen Upload nicht abgeschlossen ist, fehlt schlicht, statt
defekt zu erscheinen.

Drucken Sie also aus dieser API keine Erfüllungsquote für die Reinigung und
nennen Sie sie Compliance. Ehrlich sagen können Sie, was angekommen ist und
wann. Lesen Sie [die Anmerkung zur Vollständigkeit](https://developers.backresto.com/de/docs/support), bevor Sie
irgendjemandem eine Zahl vorlegen.
