# 场所与人员

> 这家门店、它的员工、它的区域和它的机器：每一次温度读数都挂在上面的那张固定地图。

Source: https://developers.backresto.com/zh/docs/facilities

这些是不会动的东西：餐厅本身、在里面工作的人、它被划分成的那些区域，以及那些把
食物保持在某个温度上的机器。它们没有一个是一次查验。

但它们仍然决定了那些查验意味着什么。一个[温度](https://developers.backresto.com/zh/docs/temperatures)就是一个数字，
不带任何判定，直到你从它所测的那台设备上读出阈值；而一次读数之所以属于这栋建筑的
某一部分，只是因为那台设备说了它站在哪个区域。把这一页弄错了，下游的每一个数字都
会悄无声息地错掉。

| 集合 | 权限范围 | 一条记录是什么 |
| --- | --- | --- |
| `restaurants` | `restaurants:read` | 门店本身：名称、地址、休息日、设置。 |
| `users` | `users:read` | 一个记录查验的员工账号。 |
| `areas` | `areas:read` | 门店被划分成的一个区域：厨房、冷库、吧台。 |
| `equipment` | `equipment:read` | 一台冰箱、一台冷冻柜、一台冷链设备，带着它的阈值。 |
| `sensors` | `sensors:read` | 一个无线探头，按 MAC 地址标识。 |
| `cooking-equipment` | `cooking-equipment:read` | 一台烤箱或其他加热设备，带着它的阈值。 |

这六个共用同一个[快照信封](https://developers.backresto.com/zh/docs/collections)：`id`、`deleted`、`capturedAt`、
`receivedAt`、`sequence` 以及其余字段，都摆在下文描述的 `data` 旁边。

## `restaurants`

你的密钥所限定的那家门店，以记录的形式呈现。每家餐厅一条，所以这个集合通常就只有
一页。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `name` | string, **必填** | 门店的名称，就是应用里显示的那个。 |
| `address` | string, **必填** | 邮寄地址，作为一个字符串。 |
| `closingDays` | string[], **必填** | 门店歇业的那些日子。普通字符串，schema 不对它们做约束。 |
| `exceptionalClosures` | object[] | 一次性的歇业，每一条是一个带 `from` 和 `to` 的对象。 |
| `detailedAddress` | object \| null | 拆成各部分的地址，在应用是那样存着的时候。 |
| `deliveryAddress` | string \| null | 货物送到哪里，在它与 `address` 不同的时候。 |
| `deliveryDetailedAddress` | object \| null | 同一个东西，拆成各部分。 |
| `preferredLanguage` | string \| null | 这家餐厅工作时用的语言。 |
| `multiArea` | boolean \| null | 这家门店是否被分成不止一个区域。 |
| `settings` | object \| null | 应用设置。这个结构是应用的，而且它会变。 |

```json
{
  "id": "e5a1d3c9-7b40-4a28-9df6-18c0b2e46a75",
  "collection": "restaurants",
  "deleted": false,
  "capturedAt": "1789300481002",
  "receivedAt": "1789300489517",
  "sequence": "7",
  "data": {
    "name": "Le Comptoir de Nanterre",
    "address": "12 rue des Anciennes Mairies, 92000 Nanterre",
    "closingDays": ["sunday"],
    "preferredLanguage": "fr",
    "multiArea": true
  }
}
```

这个示例只展示了运营的那一半。在它旁边，记录还带着商务的那一半：`subscription`、
`subscriptions`、`companyName`、`billingAddress`、`billingEmail`、`tvaNumber`、
`trialEndDate`、`customerId`、`discount`。它们确实存在，它们在 OpenAPI 文档里，
而这一页不会带着你一个个讲它们。

**读运营那些字段，别碰商务那些。** 它们描述的是这家餐厅与我们之间的合同，不是它
的厨房。它们不是一套计费 API，它们改变的理由与食品安全毫无关系，而一个把客户的
订阅状态回显给客户看的集成，是一张等着被开出来的工单。

**`closingDays` 是一个普通字符串的列表。** schema 没有给它们任何形状，所以读它们、
显示它们就好；不要对它们做分支，也不要假定某一种写法或大小写。

## `users`

那些记录查验的员工账号。一条[温度记录](https://developers.backresto.com/zh/docs/temperatures)的 `userId` 指向的就是
这里。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `firstName` | string, **必填** | 名，按录入的样子。 |
| `lastName` | string, **必填** | 姓，按录入的样子。 |
| `email` | string, **必填** | 这个账号的邮箱地址。 |
| `phoneNumber` | string \| null | 一个联系电话，在给了一个的时候。 |
| `modules` | string[] | 这个人用的是应用的哪些部分。自由字符串。 |
| `preferredNotificationTime` | string \| null | 这个人要求在什么时候被提醒。 |
| `isChildren` | boolean \| null | 一个内部标记。schema 没有赋予它任何含义；不要在它上面搭东西。 |
| `isDeleted` | boolean \| null | 应用自己的停用标记，而且在这里可以为 null，和别处不一样。 |

```json
{
  "id": "2774953d-8d9b-4a68-8ec4-1209edd90777",
  "collection": "users",
  "deleted": false,
  "capturedAt": "1789315002664",
  "receivedAt": "1789315010218",
  "sequence": "31",
  "data": {
    "firstName": "Amina",
    "lastName": "Berthier",
    "email": "amina.berthier@example.test",
    "phoneNumber": null,
    "modules": ["temperatures", "cleaning"],
    "isDeleted": false
  }
}
```

**这是个人数据。** `email`、`phoneNumber` 以及那两个姓名字段能识别出一个真实的
人，而它们出现在这个 API 里只有一个理由：一条食品安全记录必须说清是谁做的那次
查验。这是它们在这里唯一的职责。

所以请存 `id`，在需要一个显示名的时候再去解它。不要因为联系方式恰好出现在 payload
里，就把它们抄进你自己的表、你的日志、你的分析事件或者某个第三方工具。餐厅才是
他们员工数据的控制者，而你每做一份副本，就是他们从此要为之负责的一份。

`isDeleted` 在这个集合上可以为 null，而在别处它是必填的，所以请把一个缺失的标记
当作「未停用」，而不要让它不小心被当成假值漏过去。它与信封上的 `deleted` 之间的
区别，[在基础目录那一页上有解释](https://developers.backresto.com/zh/docs/catalogue)。

## `areas`

餐厅的一个区域。两个字段，而它的重要性远超两个字段所暗示的。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `name` | string, **必填** | 餐厅给这个区域起的名字：厨房、冷库、吧台。 |
| `isDeleted` | boolean, **必填** | 应用的停用标记。见[基础目录那一页](https://developers.backresto.com/zh/docs/catalogue)。 |

```json
{
  "id": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
  "collection": "areas",
  "deleted": false,
  "capturedAt": "1789301120440",
  "receivedAt": "1789301126871",
  "sequence": "12",
  "data": {
    "name": "Chambre froide",
    "isDeleted": false
  }
}
```

**一切按区域分组，都是靠 `areaId`。** 设备、加热设备、炸锅和清洁任务都带着一个，
而它是一次[读数](https://developers.backresto.com/zh/docs/temperatures)与这栋建筑某一部分之间唯一的关联。记录本身
上面没有 `areaId`：你得经由设备才走得到那里。

`areaId` 在它出现的每一个地方都可以为 null。一台没有区域的冰箱是正常的：它意味着
没有人给它指派过，通常是在一家 `multiArea` 为 false 的单间门店里。把这些归到
「未指派」下面，而不是把它们丢掉。

## `equipment`

冰箱、冷冻柜和其他冷链设备。决定一个温度是否可接受的，正是这条记录。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `index` | integer, **必填** | 餐厅在自己那份清单里给它的位置。显示顺序，不是一个标识符。 |
| `type` | string, **必填** | 它是哪一类设备。一个不受约束的字符串。读它，不要对它做分支。 |
| `name` | string, **必填** | 厨房管这台设备叫什么。 |
| `min` | number \| null | 可接受的最低温度。 |
| `max` | number \| null | 可接受的最高温度。 |
| `areaId` | string \| null | 它所在的那个区域。 |
| `equipmentSensorId` | string \| null | 装在它上面的那个传感器，在有一个的时候。 |
| `state` | string \| null | 这台设备当前的状态，一个不受约束的字符串。 |
| `outOfRangeCount` | number \| null | 应用维护的一个超出范围读数的计数。 |
| `isDeleted` | boolean, **必填** | 应用的停用标记。 |

```json
{
  "id": "84ce5c13-3237-40d4-901a-cbba59a6406f",
  "collection": "equipment",
  "deleted": false,
  "capturedAt": "1789302455901",
  "receivedAt": "1789302461330",
  "sequence": "58",
  "data": {
    "index": 2,
    "type": "freezer",
    "name": "Congélateur bas",
    "min": -22,
    "max": -18,
    "areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
    "equipmentSensorId": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
    "state": "ok",
    "outOfRangeCount": 0,
    "isDeleted": false
  }
}
```

**`min` 和 `max` 是你能拿到的唯一判定。** 一条[温度记录](https://developers.backresto.com/zh/docs/temperatures)带着
一个值、一个时间和它所测的那台设备。没有任何东西说它是不是没问题。-14.4 °C 在
冰箱里是一场危机，在冷冻柜里则是一个平常的星期二，而把两者分开的正是这条记录。你
搭出来的每一个合规数字，都要连接到这里。

它们同样可以为 null。当 `min` 或 `max` 缺席时，你就没有阈值，而对那次读数诚实的
回答是「未知」，不是「在范围内」。请在你的界面里这么说，而不是默认给一个绿色。

**阈值是当前的那一套，不是当时适用的那一套。** 快照给你的是今天的 `min` 和
`max`；三月的一次读数，当时是拿三月设的那套值来判断的，而餐厅随时都可以改它们。
如果你拿今天的数字去重新评估旧的读数（而这也是这个 API 唯一允许你做的事），请把
结果标注成你的重算结果，而不是厨房当时看到的东西。

**`type` 和 `state` 是不受约束的字符串。** 在 schema 里两者都只是单个的自由文本
字段，背后没有任何清单。把它们显示出来，非要分组就分组，但绝不要让一个不认识的值
从一个 switch 里漏过去、掉进沉默。

**`outOfRangeCount` 是应用的数字，不是你的。** schema 没有说它覆盖的是哪个时间
窗，而你也没法从这里的快照把它重建出来。把它当作来自应用的一个信号；如果你需要一
个自己站得住脚的计数，就从那些读数和阈值算出你自己的，并且标明它是你的。

`equipmentSensorId` 从设备指向它的传感器。传感器记录则反过来指回来。两边都可以为
null，而且不会因为一边填了，另一边就保证也填了。

## `sensors`

那些无线探头。两个字段，都是可选的。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `macAddress` | string \| null | 这个探头的硬件地址。你靠它把一台物理设备和另一台区分开。 |
| `sensorEquipmentId` | string \| null | 这个传感器看着的那台设备。 |

```json
{
  "id": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
  "collection": "sensors",
  "deleted": false,
  "capturedAt": "1789302501764",
  "receivedAt": "1789302509002",
  "sequence": "61",
  "data": {
    "macAddress": "F4:12:9D:3A:77:0B",
    "sensorEquipmentId": "84ce5c13-3237-40d4-901a-cbba59a6406f"
  }
}
```

请留意这两个字段名，它们互为镜像，很容易写反：设备带的是 `equipmentSensorId`，
传感器带的是 `sensorEquipmentId`。读错那一个，你会拿到 `undefined`，而且没有任何
报错。

这里没有名字，没有电量，也没有最后一次出现的时间。一个传感器上报了什么，在
[`temperature-readings`](https://developers.backresto.com/zh/docs/temperatures) 上，而那些是以 `equipmentId` 为键
的，不是以传感器为键。所以一个已经哑掉的探头，看起来和一个没有什么可上报的探头
一模一样。

## `cooking-equipment`

烤箱、保温柜，以及热的那一侧的其余东西。和 `equipment` 是同一个想法，只是把冷链
那套机件拿掉了。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `index` | integer, **必填** | 在餐厅自己那份清单里的显示顺序。 |
| `type` | string, **必填** | 它是哪一类设备。不受约束的字符串。 |
| `name` | string, **必填** | 厨房管这台设备叫什么。 |
| `min` | number \| null | 可接受的最低温度。 |
| `max` | number \| null | 可接受的最高温度。 |
| `areaId` | string \| null | 它所在的那个区域。 |
| `isDeleted` | boolean, **必填** | 应用的停用标记。 |

```json
{
  "id": "3d90f4c8-62b7-4e13-8a05-ff71d6b9c204",
  "collection": "cooking-equipment",
  "deleted": false,
  "capturedAt": "1789303880115",
  "receivedAt": "1789303887640",
  "sequence": "64",
  "data": {
    "index": 1,
    "type": "oven",
    "name": "Four à sole",
    "min": 63,
    "max": 260,
    "areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
    "isDeleted": false
  }
}
```

没有 `state`，没有 `outOfRangeCount`，也没有传感器：加热设备是由人来读的，所以在
两次查验之间没有任何东西盯着它们。它们的读数是
[`cooking-temperature-records`](https://developers.backresto.com/zh/docs/temperatures)，那些记录用
`cookingEquipmentId` 指到这里：这是一个和冷链那边不同的字段名，也是客户端最常悄无声
息地什么都读不到的地方。

## 读这张地图

这一页上的所有东西都很小、变化很慢，而且要先有它们，其他任何集合才说得通。

- **先加载它，然后留住它。** 在你去走任何一个记录集合之前，先走 `areas`、
  `equipment`、`cooking-equipment` 和 `sensors`，并且从你自己的副本里去解名字和
  阈值，而不是每条记录解一次。
- **但还是要刷新它。** 设备会被改名，阈值会被修正，一台冰箱会挪到另一个区域。一份
  取过一次、之后再也没走过的副本，会一路跑偏，却从来不会报错。
- **留 id，不要留副本，对人尤其如此。** 存 `userId`，在渲染的时候再去解名字。见
  上面那条关于个人数据的说明。
- **两个删除标记，到处都要看。** 信封上的 `deleted` 和 `data` 里的 `isDeleted` 是
  不同的东西，而[基础目录那一页](https://developers.backresto.com/zh/docs/catalogue)解释了哪个是哪个。
