清洁
清洁计划、真正做过的那些任务,以及证明它做过的照片——三个集合,以及一条缺失的记录证明不了什么。
应用里的清洁,是一份计划加上它的凭证。餐厅写下要清洁什么、在哪里、多久一次;员工 做完一项就勾掉一项;其中有些任务要求拍一张照片。三个集合,每一层一个,而它们之间 的连接才是大部分工作所在。
| 集合 | 权限范围 | 一条记录是什么 |
|---|---|---|
cleaning-tasks | cleaning-tasks:read | 计划里的一行:必须清洁什么、在哪里、多久一次、是否需要一张照片。 |
cleaning-task-records | cleaning-task-records:read | 一项确实做过的任务——什么时候做的,谁做的。 |
cleaning-task-pictures | cleaning-task-pictures:read | 证明它做过的那张照片。带文件。 |
这三个共用同一个快照信封:id、deleted、capturedAt、
receivedAt、sequence 以及其余字段,都摆在下文描述的 data 旁边。
cleaning-tasks
计划本身。它们很少变——一家餐厅把计划写一次,等厨房变了再改它——所以这是你遍历得 最少、缓存得最久的那个集合。
| 字段 | 类型 | 含义 |
|---|---|---|
index | integer, 必填 | 这项任务在计划中的位置,按应用给它们排的顺序。 |
name | string, 必填 | 必须清洁的是什么,用这家餐厅自己的说法。 |
description | string | null | 更长的说明,在有人写了的时候。 |
areaId | string | null | 它所属的那个区域。在一家不把自己划分成区域的餐厅里为 null。 |
recurrence | integer, 必填 | 这项任务多久轮到一次,就是一个普通整数。 |
requirePhotoProof | boolean | null | 任务要求拍照时为 true。null 表示应用从来没设置过它。 |
isDeleted | boolean, 必填 | 应用自己的软删除。请读下面那条说明。 |
{
"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 不带单位。 它是一个整数,而记录并没有说它数的是什么。请照应用
显示它的样子显示它,而不要靠猜渲染出一句「每 1 天」。
isDeleted 不是信封上的 deleted。 一项被餐厅从计划里移走的任务,回来时是
isDeleted: true 且 deleted: false:它仍然是一条活着的记录,描述的是一行已经不
再适用的内容。两个都要筛,并且把被删掉的那些留着——老记录仍然指向它们。
cleaning-task-records
一项做过的任务。有意做得很小:哪项任务、什么时候、谁。
| 字段 | 类型 | 含义 |
|---|---|---|
timestamp | string, 必填 | 这项任务是什么时候做的,以字符串表示的 Unix 毫秒时间戳。 |
cleaningTaskId | string, 必填 | 它所满足的那条 cleaning-tasks 记录。 |
userId | string | null | 做这件事的那名员工,在应用记下了一个的时候。 |
{
"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 是必填的,所以每条记录都有一项任务。反过来并不成立:一项任务可以
在计划里待上几周而没有任何记录对着它,而那正是一项每周任务在星期二时的正常状态。
cleaning-task-pictures
那张照片。它带着一个文件,这让它成为这里唯一一个你没法只靠 records 端点就消费完 的集合。
| 字段 | 类型 | 含义 |
|---|---|---|
timestamp | string, 必填 | 这张照片是什么时候拍的,以字符串表示的 Unix 毫秒时间戳。 |
cleaningTaskRecordId | string | null | 它所证明的那条 cleaning-task-records 记录。可以为 null——见下文。 |
part | number | null | 没有文档说明,而且是一个小数,不是一个计数器。请原样带着它,而不要去解读它——和追溯标签上的那条提醒一样。 |
groupId | string | null | 把那些照片绑在一起。共用一个 groupId 的记录属于同一份凭证。 |
asset | object, 必填 | 点出那个文件:objectKey 和 status 始终都有,一旦上传完成,还会有 contentType、byteLength 和 sha256。 |
{
"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…"
}
}
}
字节内容要再调一次,针对这条记录自己的 id:
GET /v1/restaurants/{restaurantId}/collections/cleaning-task-pictures/records/{recordId}/assets
它返回一组签名 URL,每个有效十五分钟,每个都自带授权,所以下载不需要任何请求
头。现在就把字节取下来,存字节而不要存链接,需要一个新 URL 的时候再调一次这个端
点。那些规则——什么会出现、sha256 是做什么用的、为什么 contentType 值得一读
——都在集合那一页上,而且在这里是一样的。
asset.status 告诉你有没有字节可取。 它是 pending 或 uploaded。一个
pending 的附件,是设备已经宣告、却还没送完的一张照片:记录是真的,文件还不是,
而 assets 端点作答时不会带上它。在你把一个空的附件列表当成一张从未拍过的照片之
前,先读这个状态。
cleaningTaskRecordId 可以为 null。 一张照片可以在不指向任何记录的情况下存
在,所以一个假定它总是在那儿的连接,会把照片丢在地上。把它们单独计数,而不要悄无
声息地扔掉。
把三个连起来
你几乎肯定想要的那个结构,是每做过一项任务一行,带上它在计划里的那一条和它的那些 照片:
cleaning-task-records.cleaningTaskId→cleaning-tasks.idcleaning-task-pictures.cleaningTaskRecordId→cleaning-task-records.id
三次遍历,在你这边做连接。没有哪个端点会替你做这件事,也没有按任务或按日期的过 滤——你把每个集合整个走完,然后对账,就像信封那一页描述的 那样。
按 capturedAt 排序,而不是 receivedAt:一台放在冷库里的平板会在找到信号时才上
传,而一张 09:00 拍下的照片可能 14:00 才到——比它所属的那条记录晚,也可能比它早。
一条缺失的记录并不意味着什么
计划里一条没有对应记录的条目,并不能证明这项任务被跳过了。 它可能是在一台还没
上传的设备上做的,也可能是在为那家餐厅开启采集之前做的。一项标着
requirePhotoProof: true 却没有照片对着它的任务也一样:那张照片可能正在上传途中,
而一个还没上传完的附件是缺席,而不是坏掉。
所以不要拿这个 API 印出一个清洁完成率,然后把它叫作合规。你能诚实地说出来的,是 什么东西到了、以及什么时候到的——在你把一个数字摆到任何人面前之前,先看 关于完整性的说明。