# 收货记录

> 收货查验和收货时拍下的照片：两个集合、每一个字段，以及它们在后厨现场意味着什么。

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

一条收货记录就是一次收货查验：一名员工接收一批订货、记录温度，并决定是否接收
它。这就是食品安全检查员会索取的那份记录。收货时拍下的照片是它旁边的第二个
集合。

| 集合 | 权限范围 | 一条记录是什么 |
| --- | --- | --- |
| `deliveries` | `deliveries:read` | 一次收货查验，每个产品带一条温度读数。 |
| `delivery-pictures` | `delivery-pictures:read` | 在一次收货中拍下的一张照片，指回那次收货。 |

两者都是普通的[集合](https://developers.backresto.com/zh/docs/collections)：`data` 外面是同一个快照信封，同样的端点、
同样的游标、同样的 90 天规则。它们带着这两个集合开通时应用里仍保有的收货历史，
而不只是那之后记下的内容。

## `deliveries`

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 查验发生的时间，以字符串表示的 Unix 毫秒时间戳。 |
| `isCompliant` | boolean, **必填** | 员工对整批收货的判定。 |
| `supplierDeliveriesId` | string \| null | 货物来自的那家[供应商](https://developers.backresto.com/zh/docs/catalogue)：一条 `suppliers` 记录的 `id`。 |
| `temperatureRecords` | array \| null | 每个被测量的产品一条。见下文。 |
| `nonComplianceReasons` | string[] \| null | 这批货被拒收、或被有保留地接收的原因。由员工自己写的自由文本；合规时为空。 |
| `correctiveActions` | string[] \| null | 为此做了什么。不需要做什么时为空。 |
| `commentary` | string \| null | 自由文本备注。常常是 `null`。 |
| `userId` | string \| null | 收下这批货的那名[员工](https://developers.backresto.com/zh/docs/facilities)，在应用记下了一个的时候。 |

### `temperatureRecords[]`

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `product` | string | 测量的是什么，按员工给它起的名字。绝不为空。 |
| `lotNumber` | string \| null | 批号或批次，在它被采集到的时候。 |
| `value` | number \| null | 读数。产品在没有测量的情况下被登记时为 `null`。 |
| `unit` | `"C"` | 始终是摄氏度。见下文。 |

**温度始终是摄氏度。** 一家应用以华氏度记录的餐厅，发来的仍是华氏度读数，它们在
到达时、存储之前就被换算。每一条的 `unit` 都是 `"C"`，所以使用方永远不必换算，也
不必去猜。

```json
{
  "id": "8f2c1b04-0d5a-4b7e-9f31-6ad2c0e77a51",
  "collection": "deliveries",
  "deleted": false,
  "capturedAt": "1787932805120",
  "receivedAt": "1787932911004",
  "sequence": "88",
  "data": {
    "timestamp": "1787932800000",
    "isCompliant": false,
    "supplierDeliveriesId": "supplier-7",
    "temperatureRecords": [
      { "product": "Poulet fermier", "lotNumber": "L2291", "value": 6.4, "unit": "C" },
      { "product": "Farine T55", "lotNumber": null, "value": null, "unit": "C" }
    ],
    "nonComplianceReasons": ["Température trop élevée"],
    "correctiveActions": ["Produit refusé", "Fournisseur prévenu"],
    "commentary": "Camion en retard, rupture de chaîne du froid probable",
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

## `delivery-pictures`

一张标签、一个破损的托盘、一支抵着展示柜的探针：这些照片就是 `isCompliant` 背后的
证据。每一张都是一条指向其收货记录的记录，而且它带着一个文件，所以字节内容来自
assets 端点。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `timestamp` | string, **必填** | 这张照片是什么时候拍的，以字符串表示的 Unix 毫秒时间戳。 |
| `deliveryId` | string \| null | 它所属的那条 `deliveries` 记录。 |
| `part` | number \| null | 应用把一张照片分成几部分存储时，这条记录在其中的位置。请原样带着它，而不要去解读它。 |
| `groupId` | string \| null | 把那几部分绑在一起。共用一个 `groupId` 的记录是同一张照片。 |
| `asset` | object, **必填** | 点出那个文件：`objectKey` 和 `status` 始终都有，一旦上传完成，还会有 `contentType`、`byteLength` 和 `sha256`。 |

```json
{
  "id": "c61e0a7d-35b2-4f89-a1d4-7e90b2c45f18",
  "collection": "delivery-pictures",
  "deleted": false,
  "capturedAt": "1787932861240",
  "receivedAt": "1787932990517",
  "sequence": "143",
  "data": {
    "timestamp": "1787932860000",
    "deliveryId": "8f2c1b04-0d5a-4b7e-9f31-6ad2c0e77a51",
    "part": 4,
    "groupId": "b4d81f0a-c2e7-4a55-9d10-6f3e8a2b7c91",
    "asset": {
      "objectKey": "delivery-pictures/b4d81f0a/4.jpg",
      "status": "uploaded",
      "contentType": "image/jpeg",
      "byteLength": 902450,
      "sha256": "9f2a…"
    }
  }
}
```

字节内容要再调一次，针对这条记录自己的 id：

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

它返回一组有效期十五分钟的签名 URL，与[集合那一页](https://developers.backresto.com/zh/docs/collections)上描述的完全
一样。对一张照片的任何一部分发起请求，你拿到的都是同一个文件：API 会替你解析这个
分组，所以你不需要弄清楚文件在哪一部分里。

要展示一次收货的照片，就遍历 `delivery-pictures`，留下 `deliveryId` 等于那条收货
记录 `id` 的记录，每个 `groupId` 留一条。

## 正确地读它

**`isCompliant` 是记录，不是计算结果。** 它是收货的那个人做出的决定，可能与你只看
温度会得出的结论不一致：一次用表面探针取得的读数、一个有自己阈值的产品、一次
临场判断。请把它呈现为他们的判定。如果你想要自己的判定，把它算在他们的旁边，并
标明那是你的。

**自由文本字段就是自由文本。** `nonComplianceReasons`、`correctiveActions` 和
`product` 是员工在营业中打出来的，用的是这家餐厅的语言，带着一个忙碌周二的拼写。
不要拿它们建枚举，不要用它们做连接键，也不要假定它们是法语。

**`value` 可以是 `null`。** 一个产品被登记却没有读数是正常的。它往往是干货。把
`null` 当作「未测量」，绝不要当作 `0`。

**时间戳是字符串。** `timestamp` 是 Unix 毫秒时间戳的十进制字符串，因为整个
BackResto 平台就是这样存时间戳的。`Number(timestamp)` 今天是安全的，往后几十万年
也是安全的，但不要让一个 JSON 解析器悄悄把它转成数字，然后再以丢了精度的样子写
回去。

**一张照片可能晚于它的收货记录到达。** 手机在能上传的时候才上传照片，有时是在查验
本身之后几分钟。一条暂时还没有照片的收货记录，可能只是还有一张在路上；在断定它
没有照片之前，晚些再遍历一次。
