# 冷却与食品处理流程

> 冷却、冷冻、复热与运输：记录一次温度转变的四个集合，以及一次没做完的转变长什么样。

Source: https://developers.backresto.com/zh/docs/food-processes

一道慢慢冷下来的热菜，会在细菌繁殖的那个温度区间里待上好几个小时，所以规矩是把
它快速降下来，并且能拿得出证据说你做了。有四个集合记录这类转变。其中三个是时间
上的转变（降下来、再降下去、又升回去），第四个则是同一个想法，发生在空间上。

| 集合 | 权限范围 | 一条记录是什么 |
| --- | --- | --- |
| `cooling` | `cooling:read` | 一件被降到冷藏温度的热产品。 |
| `freezing` | `freezing:read` | 一件被降到冷冻状态的产品。 |
| `reheating` | `reheating:read` | 一件被升回供餐温度的冷产品。 |
| `transport` | `transport:read` | 一件从一处被运到另一处的产品，带着两端各自的温度。 |

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

## `cooling`

一次循环：一件产品，什么时候开始、当时有多热，什么时候结束、当时有多冷。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 这条记录自己的时间戳，以字符串表示的 Unix 毫秒时间戳。不是这次循环的时钟。那是下面那两个日期。 |
| `product` | string, **必填** | 被冷却的是什么，以文本记录。 |
| `beginDate` | string, **必填** | 这次循环什么时候开始，以字符串表示的 Unix 毫秒时间戳。 |
| `beginTemperature` | number, **必填** | 它开始时的温度。 |
| `endDate` | string \| null | 它什么时候结束。还没结束时为 `null`。 |
| `endTemperature` | number \| null | 它结束时的温度。与 `endDate` 一同为 `null`。 |
| `reminderId` | string \| null | 应用挂在这次循环上的那个提醒，在有一个的时候。 |
| `userId` | string \| null | 记录它的那名[员工](https://developers.backresto.com/zh/docs/facilities)，在应用记下了一个的时候。 |

```json
{
  "id": "0e5d1a7c-9f24-4b13-8d60-c1a7f0b25e94",
  "collection": "cooling",
  "deleted": false,
  "capturedAt": "1789779612441",
  "receivedAt": "1789779910228",
  "sequence": "1",
  "data": {
    "timestamp": "1789772400000",
    "product": "Blanquette de veau",
    "beginDate": "1789772400000",
    "beginTemperature": 63.5,
    "endDate": "1789779600000",
    "endTemperature": 8.2,
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

**`product` 是一个名字，不是一个引用。** 这些记录上没有 `productId`，所以你没法在
不做字符串匹配的情况下把它们连到 `products` 上，而这些字符串是在营业中、用这家
餐厅的语言打出来的。

**`reminderId` 是不透明的。** 这个 API 里没有任何集合能解开它。如果它有助于你把
记录分组，就带着它；但别指望能查到它指向什么。

## `freezing`

同样的结构，用于一件被降到冷冻、而不是降到冷藏温度的产品。逐字段看，它就是
`cooling`。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 这条记录自己的时间戳。 |
| `product` | string, **必填** | 被冷冻的是什么。 |
| `beginDate` | string, **必填** | 这次操作什么时候开始。 |
| `beginTemperature` | number, **必填** | 它开始时的温度。 |
| `endDate` | string \| null | 它什么时候结束，或者 `null`。 |
| `endTemperature` | number \| null | 它结束时的温度，或者 `null`。 |
| `reminderId` | string \| null | 同上。 |
| `userId` | string \| null | 是谁记录的。 |

记录里没有任何东西说它是这三者中的哪一个：那取决于它来自哪个集合，而信封的
`collection` 字段会把它复述出来。一条把三者并进同一张表的流水线，必须把那一列
留住。

## `reheating`

一件被升回来的冷产品。结构还是那个，只是读的方向反过来：`beginTemperature` 是低
的那个，`endTemperature` 是高的那个。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 这条记录自己的时间戳。 |
| `product` | string, **必填** | 被复热的是什么。 |
| `beginDate` | string, **必填** | 它什么时候进去的。 |
| `beginTemperature` | number, **必填** | 它开始时的温度。 |
| `endDate` | string \| null | 它什么时候出来的，或者 `null`。 |
| `endTemperature` | number \| null | 它达到的温度，或者 `null`。 |
| `reminderId` | string \| null | 同上。 |
| `userId` | string \| null | 是谁记录的。 |

一次没有结尾的操作长这样：

```json
{
  "id": "b4c8e21d-3a76-4f58-9c02-77ad5e1b8f30",
  "collection": "reheating",
  "deleted": false,
  "capturedAt": "1789783221907",
  "receivedAt": "1789783402115",
  "sequence": "2",
  "data": {
    "timestamp": "1789783200000",
    "product": "Gratin dauphinois",
    "beginDate": "1789783200000",
    "beginTemperature": 4.1,
    "endDate": null,
    "endTemperature": null,
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

## `transport`

同一种转变，发生在空间而不是时间上：产品从哪里出发、到达了哪里，两端各有一个
温度。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 这条记录自己的时间戳。 |
| `product` | string, **必填** | 被运输的是什么，以文本记录。 |
| `departureLocation` | string, **必填** | 它从哪里出发，以文本记录。 |
| `departureTime` | string, **必填** | 它什么时候出发，以字符串表示的 Unix 毫秒时间戳。 |
| `departureTemperature` | number, **必填** | 出发时的温度。 |
| `arrivalLocation` | string \| null | 它到达了哪里。还没到达时为 `null`。 |
| `arrivalTime` | string \| null | 它什么时候到达，或者 `null`。 |
| `arrivalTemperature` | number \| null | 到达时的温度，或者 `null`。 |
| `reminderId` | string \| null | 同上。 |
| `userId` | string \| null | 是谁记录的。 |

```json
{
  "id": "f31b6a08-5c47-4de9-b1a5-6e0c9d247b82",
  "collection": "transport",
  "deleted": false,
  "capturedAt": "1789786944610",
  "receivedAt": "1789787101883",
  "sequence": "3",
  "data": {
    "timestamp": "1789785000000",
    "product": "Plats préparés, service traiteur",
    "departureLocation": "Cuisine centrale",
    "departureTime": "1789785000000",
    "departureTemperature": 3.4,
    "arrivalLocation": "Salle Montparnasse",
    "arrivalTime": "1789786800000",
    "arrivalTemperature": 5.9,
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

这两个地点是自由文本，既不是[区域](https://developers.backresto.com/zh/docs/facilities)，也不是地址。它们就是那个人
打进去的东西；而到达那一端的可选程度，和到达温度完全一样：一条记录可以只带着一次
出发，别的什么都没有。

## 拿它们做事

**一个为 null 的结尾，是两件不同的事。** `endDate` 和 `endTemperature`（以及
`transport` 上的 `arrivalTime`、`arrivalTemperature` 和 `arrivalLocation`）都可以
为 null，而记录不会告诉你是为什么。可能是一次在你读它的时候仍在进行的操作，也可能
是一次从来没有人去收尾的操作。两者看起来一模一样。

这件事在你要算时长的那一刻就变得要紧。这些日期是字符串，所以
`endDate - beginDate` 在一条已结束的记录上减得好好的，而在一条未结束的记录上，它
会把 `null` 强制转成零，然后交给你一个很大的负数，而不是一个错误。没有任何东西会
抛出异常；你的平均值只是错的。先筛出已结束的记录，把未结束的单独计数，并且自己
决定一条未结束的记录到了多久就不再算进行中、而开始算被丢下了。这个 API 里没有
任何东西会替你做这个判断。

**用 `beginDate` 和 `endDate` 来算，而不是 `timestamp`。** 这两个日期是这次操作的
时钟。`timestamp` 是这条记录自己的时钟，而一段时长不是用它做出来的。

**报表用 `capturedAt`，同步用 `receivedAt`。** 一台放在冷库里的平板会在找到信号时
才上传，所以一次 09:00 开始的循环可能 14:00 才到。
[信封那一页](https://developers.backresto.com/zh/docs/collections)讲了完整的来龙去脉。

**缺席证明不了什么。** 一件没有冷却记录的产品，可能从来没被冷却过，也可能正躺在
一台还没上传的设备里。不要把这类记录的计数变成一个合规数字。见
[那条提醒](https://developers.backresto.com/zh/docs/support)。
